realfft-3.5.0/.cargo_vcs_info.json0000644000000001360000000000100124640ustar { "git": { "sha1": "d0d4eee0525fd27c96c8a046d6d107acd5ed84a6" }, "path_in_vcs": "" }realfft-3.5.0/.github/workflows/publish.yml000064400000000000000000000007551046102023000170510ustar 00000000000000on: push: tags: - '*' name: Publish on crates.io jobs: publish: name: Publish runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy - name: Publish run: cargo publish --token ${CARGO_REGISTRY_TOKEN} env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} realfft-3.5.0/.github/workflows/run_tests.yml000064400000000000000000000024231046102023000174230ustar 00000000000000on: [pull_request] name: CI jobs: check_and_test: name: Check+Test runs-on: ubuntu-latest strategy: matrix: rust: - stable - beta - nightly steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@v1 with: components: rustfmt, clippy toolchain: ${{ matrix.rust }} - name: Run cargo check run: cargo check - name: Run cargo test run: cargo test check_msrv: name: Check minimum rust version runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v3 - name: Install toolchain uses: dtolnay/rust-toolchain@1.61 with: components: rustfmt, clippy - name: Run cargo check run: cargo check fmt: name: Lints runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy - name: Run cargo fmt run: cargo fmt --all -- --check - name: Run cargo clippy run: cargo clippy -- -D warnings realfft-3.5.0/.gitignore000064400000000000000000000000231046102023000132370ustar 00000000000000/target Cargo.lock realfft-3.5.0/Cargo.lock0000644000000464340000000000100104520ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bumpalo" version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "criterion" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "itertools 0.13.0", "num-traits", "oorandom", "plotters", "rayon", "regex", "serde", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools 0.10.5", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi", ] [[package]] name = "half" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "num-complex" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "plotters" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "primal-check" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" dependencies = [ "num-integer", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rand" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "realfft" version = "3.5.0" dependencies = [ "criterion", "rand", "rustfft", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustfft" version = "6.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f140db74548f7c9d7cce60912c9ac414e74df5e718dc947d514b051b42f3f4" dependencies = [ "num-complex", "num-integer", "num-traits", "primal-check", "strength_reduce", "transpose", ] [[package]] name = "rustversion" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "strength_reduce" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" [[package]] name = "syn" version = "2.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "transpose" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" dependencies = [ "num-integer", "strength_reduce", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] [[package]] name = "zerocopy" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", "syn", ] realfft-3.5.0/Cargo.toml0000644000000027610000000000100104700ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "realfft" version = "3.5.0" authors = ["HEnquist "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Real-to-complex forward FFT and complex-to-real inverse FFT for Rust" readme = "README.md" keywords = [ "fft", "dft", "discrete", "fourier", "transform", ] categories = [ "algorithms", "compression", "multimedia::encoding", "science", ] license = "MIT" repository = "https://github.com/HEnquist/realfft" [features] avx = ["rustfft/avx"] default = ["rustfft/default"] neon = ["rustfft/neon"] sse = ["rustfft/sse"] wasm_simd = ["rustfft/wasm_simd"] [lib] name = "realfft" path = "src/lib.rs" [[example]] name = "concurrency" path = "examples/concurrency.rs" [[bench]] name = "realfft" path = "benches/realfft.rs" harness = false [dependencies.rustfft] version = "6.4.0" default-features = false [dev-dependencies.criterion] version = "0.6" [dev-dependencies.rand] version = "0.9" realfft-3.5.0/Cargo.toml.orig000064400000000000000000000017361046102023000141520ustar 00000000000000[package] name = "realfft" version = "3.5.0" authors = ["HEnquist "] edition = "2018" description = "Real-to-complex forward FFT and complex-to-real inverse FFT for Rust" license = "MIT" repository = "https://github.com/HEnquist/realfft" keywords = ["fft", "dft", "discrete", "fourier", "transform"] categories = ["algorithms", "compression", "multimedia::encoding", "science"] readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] # Map RealFFT's features one-to-one with RustFFT's. For more information, refer # to the Feature Flags section at https://docs.rs/rustfft/latest/rustfft/ default = ["rustfft/default"] avx = ["rustfft/avx"] sse = ["rustfft/sse"] neon = ["rustfft/neon"] wasm_simd = ["rustfft/wasm_simd"] [dependencies] rustfft = { version = "6.4.0", default-features = false } [dev-dependencies] criterion = "0.6" rand = "0.9" [[bench]] name = "realfft" harness = false realfft-3.5.0/README.md000064400000000000000000000146601046102023000125420ustar 00000000000000# RealFFT: Real-to-complex forward FFT and complex-to-real inverse FFT based on RustFFT This library is a wrapper for [RustFFT](https://crates.io/crates/rustfft) that enables fast and convenient FFT of real-valued data. The API is designed to be as similar as possible to RustFFT. Also, the [feature flags](#cargo-features) are passed on to RustFFT, allowing selection of its features – they do not affect RealFFT itself. Using this library instead of RustFFT directly avoids the need of converting real-valued data to complex before performing a FFT. If the length is even, it also enables faster computations by using a complex FFT of half the length. It then packs a real-valued signal of length N into an N/2 long complex buffer, which is transformed using a standard complex-to-complex FFT. The FFT result is then post-processed to give only the first half of the complex spectrum, as an N/2+1 long complex vector. The inverse FFT goes through the same steps backwards, to transform a complex spectrum of length N/2+1 to a real-valued signal of length N. The speed increase compared to just converting the input to a length N complex vector and using a length N complex-to-complex FFT depends on the length of the input data. The largest improvements are for longer FFTs and for lengths over around 1000 elements there is an improvement of about a factor 2. The difference shrinks for shorter lengths, and around 30 elements there is no longer any difference. ## Why use real-to-complex FFT? ### Using a complex-to-complex FFT A simple way to transform a real valued signal is to convert it to complex, and then use a complex-to-complex FFT. Let's assume `x` is a 6 element long real vector: ```text x = [x0r, x1r, x2r, x3r, x4r, x5r] ``` We now convert `x` to complex by adding an imaginary part with value zero. Using the notation `(xNr, xNi)` for the complex value `xN`, this becomes: ```text x_c = [(x0r, 0), (x1r, 0), (x2r, 0), (x3r, 0), (x4r, 0), (x5r, 0)] ``` Performing a normal complex FFT, the result of `FFT(x_c)` is: ```text FFT(x_c) = [(X0r, X0i), (X1r, X1i), (X2r, X2i), (X3r, X3i), (X4r, X4i), (X5r, X5i)] ``` But because our `x_c` is real-valued (all imaginary parts are zero), some of this becomes redundant: ```text FFT(x_c) = [(X0r, 0), (X1r, X1i), (X2r, X2i), (X3r, 0), (X2r, -X2i), (X1r, -X1i)] ``` The last two values are the complex conjugates of `X1` and `X2`. Additionally, `X0i` and `X3i` are zero. As we can see, the output contains 6 independent values, and the rest is redundant. But it still takes time for the FFT to calculate the redundant values. Converting the input data to complex also takes a little bit of time. If the length of `x` instead had been 7, result would have been: ```text FFT(x_c) = [(X0r, 0), (X1r, X1i), (X2r, X2i), (X3r, X3i), (X3r, -X3i), (X2r, -X2i), (X1r, -X1i)] ``` The result is similar, but this time there is no zero at `X3i`. Also in this case we got the same number of independent values as we started with. ### Real-to-complex Using a real-to-complex FFT removes the need for converting the input data to complex. It also avoids calculating the redundant output values. The result for 6 elements is: ```text RealFFT(x) = [(X0r, 0), (X1r, X1i), (X2r, X2i), (X3r, 0)] ``` The result for 7 elements is: ```text RealFFT(x) = [(X0r, 0), (X1r, X1i), (X2r, X2i), (X3r, X3i)] ``` This is the data layout output by the real-to-complex FFT, and the one expected as input to the complex-to-real inverse FFT. ## Scaling RealFFT matches the behaviour of RustFFT and does not normalize the output of either forward or inverse FFT. To get normalized results, each element must be scaled by `1/sqrt(length)`, where `length` is the length of the real-valued signal. If the processing involves both an FFT and an iFFT step, it is advisable to merge the two normalization steps to a single, by scaling by `1/length`. ## Documentation The full documentation can be generated by rustdoc. To generate and view it run: ```sh cargo doc --open ``` ## Benchmarks To run a set of benchmarks comparing real-to-complex FFT with standard complex-to-complex, type: ```sh cargo bench ``` The results are printed while running, and are compiled into an html report containing much more details. To view, open `target/criterion/report/index.html` in a browser. ## Example Transform a signal, and then inverse transform the result. ```rust use realfft::RealFftPlanner; use rustfft::num_complex::Complex; use rustfft::num_traits::Zero; let length = 256; // make a planner let mut real_planner = RealFftPlanner::::new(); // create a FFT let r2c = real_planner.plan_fft_forward(length); // make a dummy real-valued signal (filled with zeros) let mut indata = r2c.make_input_vec(); // make a vector for storing the spectrum let mut spectrum = r2c.make_output_vec(); // Are they the length we expect? assert_eq!(indata.len(), length); assert_eq!(spectrum.len(), length/2+1); // forward transform the signal r2c.process(&mut indata, &mut spectrum).unwrap(); // create an inverse FFT let c2r = real_planner.plan_fft_inverse(length); // create a vector for storing the output let mut outdata = c2r.make_output_vec(); assert_eq!(outdata.len(), length); // inverse transform the spectrum back to a real-valued signal c2r.process(&mut spectrum, &mut outdata).unwrap(); ``` ## Cargo features RealFFT has the same set of cargo feature flags as RustFFT. These features are used to control the features of RustFFT and they do not affect RealFFT itself. The default is to enable the default features of RustFFT. See the [RustFFT documentation](https://docs.rs/rustfft/latest/rustfft/#feature-flags) for more details. ## Versions - 3.5.0 - Add cargo features to set the corresponding RustFFT features. - 3.4.0 - Fix undefined behavior reported by Miri. - Update to latest RustFFT. - 3.3.0 - Add method for getting the length of the complex input/output. - Bugfix: clean up numerical noise in the zero imaginary components. - 3.2.0 - Allow scratch buffer to be larger than needed. - 3.1.0 - Update to RustFFT 6.1 with Neon support. - 3.0.2 - Fix confusing typos in errors about scratch length. - 3.0.1 - More helpful error messages, fix confusing typos. - 3.0.0 - Improved error reporting. - 2.0.1 - Minor bugfix. - 2.0.0 - Update RustFFT to 6.0.0 and num-complex to 0.4.0. - 1.1.0 - Add missing Sync+Send. - 1.0.0 - First version with new api. #### Compatibility The `realfft` crate has the same rustc version requirements as RustFFT. The minimum rustc version is 1.61. License: MIT realfft-3.5.0/benches/realfft.rs000064400000000000000000000102451046102023000146560ustar 00000000000000use criterion::{criterion_group, criterion_main, Bencher, BenchmarkId, Criterion}; extern crate realfft; extern crate rustfft; use realfft::RealFftPlanner; use rustfft::num_complex::Complex; /// Times just the FFT execution (not allocation and pre-calculation) /// for a given length fn bench_fft(b: &mut Bencher, len: usize) { let mut planner = rustfft::FftPlanner::new(); let fft = planner.plan_fft_forward(len); let mut scratch = vec![Complex::from(0.0); fft.get_outofplace_scratch_len()]; let mut signal = vec![ Complex { re: 0_f64, im: 0_f64 }; len ]; let mut spectrum = signal.clone(); b.iter(|| fft.process_outofplace_with_scratch(&mut signal, &mut spectrum, &mut scratch)); } fn bench_realfft(b: &mut Bencher, len: usize) { let mut planner = RealFftPlanner::::new(); let fft = planner.plan_fft_forward(len); let mut signal = vec![0_f64; len]; let mut spectrum = vec![ Complex { re: 0_f64, im: 0_f64 }; len / 2 + 1 ]; let mut scratch = vec![Complex::from(0.0); fft.get_scratch_len()]; b.iter(|| fft.process_with_scratch(&mut signal, &mut spectrum, &mut scratch)); } /// Times just the FFT execution (not allocation and pre-calculation) /// for a given length fn bench_ifft(b: &mut Bencher, len: usize) { let mut planner = rustfft::FftPlanner::new(); let fft = planner.plan_fft_inverse(len); let mut scratch = vec![Complex::from(0.0); fft.get_outofplace_scratch_len()]; let mut signal = vec![ Complex { re: 0_f64, im: 0_f64 }; len ]; let mut spectrum = signal.clone(); b.iter(|| fft.process_outofplace_with_scratch(&mut signal, &mut spectrum, &mut scratch)); } fn bench_realifft(b: &mut Bencher, len: usize) { let mut planner = RealFftPlanner::::new(); let fft = planner.plan_fft_inverse(len); let mut signal = vec![0_f64; len]; let mut spectrum = vec![ Complex { re: 0_f64, im: 0_f64 }; len / 2 + 1 ]; let mut scratch = vec![Complex::from(0.0); fft.get_scratch_len()]; b.iter(|| fft.process_with_scratch(&mut spectrum, &mut signal, &mut scratch)); } fn bench_pow2_fw(c: &mut Criterion) { let mut group = c.benchmark_group("Fw Powers of 2"); for i in [8, 16, 32, 64, 128, 256, 1024, 4096, 65536].iter() { group.bench_with_input(BenchmarkId::new("Complex", i), i, |b, i| bench_fft(b, *i)); group.bench_with_input(BenchmarkId::new("Real", i), i, |b, i| bench_realfft(b, *i)); } group.finish(); } fn bench_pow2_inv(c: &mut Criterion) { let mut group = c.benchmark_group("Inv Powers of 2"); for i in [8, 16, 32, 64, 128, 256, 1024, 4096, 65536].iter() { group.bench_with_input(BenchmarkId::new("Complex", i), i, |b, i| bench_ifft(b, *i)); group.bench_with_input(BenchmarkId::new("Real", i), i, |b, i| bench_realifft(b, *i)); } group.finish(); } //fn bench_pow7(c: &mut Criterion) { // let mut group = c.benchmark_group("Powers of 7"); // for i in [2 * 343, 2 * 2401, 2 * 16807].iter() { // group.bench_with_input(BenchmarkId::new("Complex", i), i, |b, i| bench_fft(b, *i)); // group.bench_with_input(BenchmarkId::new("Real", i), i, |b, i| bench_realfft(b, *i)); // } // group.finish(); //} fn bench_range_fw(c: &mut Criterion) { let mut group = c.benchmark_group("Fw Range 1022-1025"); for i in 1022..1026 { group.bench_with_input(BenchmarkId::new("Complex", i), &i, |b, i| bench_fft(b, *i)); group.bench_with_input(BenchmarkId::new("Real", i), &i, |b, i| bench_realfft(b, *i)); } group.finish(); } fn bench_range_inv(c: &mut Criterion) { let mut group = c.benchmark_group("Inv Range 1022-1025"); for i in 1022..1026 { group.bench_with_input(BenchmarkId::new("Complex", i), &i, |b, i| bench_ifft(b, *i)); group.bench_with_input(BenchmarkId::new("Real", i), &i, |b, i| { bench_realifft(b, *i) }); } group.finish(); } criterion_group!( benches, bench_pow2_fw, bench_range_fw, bench_pow2_inv, bench_range_inv ); criterion_main!(benches); realfft-3.5.0/examples/concurrency.rs000064400000000000000000000012471046102023000157760ustar 00000000000000//! Show how to use a FFT in multiple threads use std::sync::Arc; use std::thread; use realfft::RealFftPlanner; fn main() { let mut planner = RealFftPlanner::::new(); let fft = planner.plan_fft_forward(100); let threads: Vec> = (0..2) .map(|_| { let fft_copy = Arc::clone(&fft); thread::spawn(move || { let mut data = fft_copy.make_input_vec(); let mut output = fft_copy.make_output_vec(); fft_copy.process(&mut data, &mut output).unwrap(); }) }) .collect(); for thread in threads { thread.join().unwrap(); } } realfft-3.5.0/src/lib.rs000064400000000000000000001134671046102023000131730ustar 00000000000000#![doc = include_str!("../README.md")] pub use rustfft::num_complex; pub use rustfft::num_traits; pub use rustfft::FftNum; use rustfft::num_complex::Complex; use rustfft::num_traits::Zero; use rustfft::FftPlanner; use std::collections::HashMap; use std::error; use std::fmt; use std::sync::Arc; type Res = Result; /// Custom error returned by FFTs pub enum FftError { /// The input buffer has the wrong size. The transform was not performed. /// /// The first member of the tuple is the expected size and the second member is the received /// size. InputBuffer(usize, usize), /// The output buffer has the wrong size. The transform was not performed. /// /// The first member of the tuple is the expected size and the second member is the received /// size. OutputBuffer(usize, usize), /// The scratch buffer has the wrong size. The transform was not performed. /// /// The first member of the tuple is the minimum size and the second member is the received /// size. ScratchBuffer(usize, usize), /// The input data contained a non-zero imaginary part where there should have been a zero. /// The transform was performed, but the result may not be correct. /// /// The first member of the tuple represents the first index of the complex buffer and the /// second member represents the last index of the complex buffer. The values are set to true /// if the corresponding complex value contains a non-zero imaginary part. InputValues(bool, bool), } impl FftError { fn fmt_internal(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let desc = match self { Self::InputBuffer(expected, got) => { format!("Wrong length of input, expected {}, got {}", expected, got) } Self::OutputBuffer(expected, got) => { format!("Wrong length of output, expected {}, got {}", expected, got) } Self::ScratchBuffer(expected, got) => { format!( "Scratch buffer of size {} is too small, must be at least {} long", got, expected ) } Self::InputValues(first, last) => match (first, last) { (true, false) => "Imaginary part of first value was non-zero.".to_string(), (false, true) => "Imaginary part of last value was non-zero.".to_string(), (true, true) => { "Imaginary parts of both first and last values were non-zero.".to_string() } (false, false) => unreachable!(), }, }; write!(f, "{}", desc) } } impl fmt::Debug for FftError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.fmt_internal(f) } } impl fmt::Display for FftError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.fmt_internal(f) } } impl error::Error for FftError {} fn compute_twiddle(index: usize, fft_len: usize) -> Complex { let constant = -2f64 * std::f64::consts::PI / fft_len as f64; let angle = constant * index as f64; Complex { re: T::from_f64(angle.cos()).unwrap(), im: T::from_f64(angle.sin()).unwrap(), } } pub struct RealToComplexOdd { length: usize, fft: std::sync::Arc>, scratch_len: usize, } pub struct RealToComplexEven { twiddles: Vec>, length: usize, fft: std::sync::Arc>, scratch_len: usize, } pub struct ComplexToRealOdd { length: usize, fft: std::sync::Arc>, scratch_len: usize, } pub struct ComplexToRealEven { twiddles: Vec>, length: usize, fft: std::sync::Arc>, scratch_len: usize, } /// A forward FFT that takes a real-valued input signal of length N /// and transforms it to a complex spectrum of length N/2+1. #[allow(clippy::len_without_is_empty)] pub trait RealToComplex: Sync + Send { /// Transform a signal of N real-valued samples, /// storing the resulting complex spectrum in the N/2+1 /// (with N/2 rounded down) element long output slice. /// The input buffer is used as scratch space, /// so the contents of input should be considered garbage after calling. /// It also allocates additional scratch space as needed. /// An error is returned if any of the given slices has the wrong length. fn process(&self, input: &mut [T], output: &mut [Complex]) -> Res<()>; /// Transform a signal of N real-valued samples, /// similar to [`process()`](RealToComplex::process). /// The difference is that this method uses the provided /// scratch buffer instead of allocating new scratch space. /// This is faster if the same scratch buffer is used for multiple calls. fn process_with_scratch( &self, input: &mut [T], output: &mut [Complex], scratch: &mut [Complex], ) -> Res<()>; /// Get the minimum length of the scratch buffer needed for `process_with_scratch`. fn get_scratch_len(&self) -> usize; /// The FFT length. /// Get the length of the real signal that this FFT takes as input. fn len(&self) -> usize; /// Get the number of complex data points that this FFT returns. fn complex_len(&self) -> usize { self.len() / 2 + 1 } /// Convenience method to make an input vector of the right type and length. fn make_input_vec(&self) -> Vec; /// Convenience method to make an output vector of the right type and length. fn make_output_vec(&self) -> Vec>; /// Convenience method to make a scratch vector of the right type and length. fn make_scratch_vec(&self) -> Vec>; } /// An inverse FFT that takes a complex spectrum of length N/2+1 /// and transforms it to a real-valued signal of length N. #[allow(clippy::len_without_is_empty)] pub trait ComplexToReal: Sync + Send { /// Inverse transform a complex spectrum corresponding to a real-valued signal of length N. /// The input is a slice of complex values with length N/2+1 (with N/2 rounded down). /// The resulting real-valued signal is stored in the output slice of length N. /// The input buffer is used as scratch space, /// so the contents of input should be considered garbage after calling. /// It also allocates additional scratch space as needed. /// An error is returned if any of the given slices has the wrong length. /// If the input data is invalid, meaning that one of the positions that should /// contain a zero holds a non-zero value, the transform is still performed. /// The function then returns an `FftError::InputValues` error to tell that the /// result may not be correct. fn process(&self, input: &mut [Complex], output: &mut [T]) -> Res<()>; /// Inverse transform a complex spectrum, /// similar to [`process()`](ComplexToReal::process). /// The difference is that this method uses the provided /// scratch buffer instead of allocating new scratch space. /// This is faster if the same scratch buffer is used for multiple calls. fn process_with_scratch( &self, input: &mut [Complex], output: &mut [T], scratch: &mut [Complex], ) -> Res<()>; /// Get the minimum length of the scratch space needed for `process_with_scratch`. fn get_scratch_len(&self) -> usize; /// The FFT length. /// Get the length of the real-valued signal that this FFT returns. fn len(&self) -> usize; /// Get the length of the slice slice of complex values that this FFT accepts as input. fn complex_len(&self) -> usize { self.len() / 2 + 1 } /// Convenience method to make an input vector of the right type and length. fn make_input_vec(&self) -> Vec>; /// Convenience method to make an output vector of the right type and length. fn make_output_vec(&self) -> Vec; /// Convenience method to make a scratch vector of the right type and length. fn make_scratch_vec(&self) -> Vec>; } fn zip3(a: A, b: B, c: C) -> impl Iterator where A: IntoIterator, B: IntoIterator, C: IntoIterator, { a.into_iter() .zip(b.into_iter().zip(c)) .map(|(x, (y, z))| (x, y, z)) } /// A planner is used to create FFTs. /// It caches results internally, /// so when making more than one FFT it is advisable to reuse the same planner. pub struct RealFftPlanner { planner: FftPlanner, r2c_cache: HashMap>>, c2r_cache: HashMap>>, } impl RealFftPlanner { /// Create a new planner. pub fn new() -> Self { let planner = FftPlanner::::new(); Self { r2c_cache: HashMap::new(), c2r_cache: HashMap::new(), planner, } } /// Plan a real-to-complex forward FFT. Returns the FFT in a shared reference. /// If requesting a second forward FFT of the same length, /// the planner will return a new reference to the already existing one. pub fn plan_fft_forward(&mut self, len: usize) -> Arc> { if let Some(fft) = self.r2c_cache.get(&len) { Arc::clone(fft) } else { let fft = if len % 2 > 0 { Arc::new(RealToComplexOdd::new(len, &mut self.planner)) as Arc> } else { Arc::new(RealToComplexEven::new(len, &mut self.planner)) as Arc> }; self.r2c_cache.insert(len, Arc::clone(&fft)); fft } } /// Plan a complex-to-real inverse FFT. Returns the FFT in a shared reference. /// If requesting a second inverse FFT of the same length, /// the planner will return a new reference to the already existing one. pub fn plan_fft_inverse(&mut self, len: usize) -> Arc> { if let Some(fft) = self.c2r_cache.get(&len) { Arc::clone(fft) } else { let fft = if len % 2 > 0 { Arc::new(ComplexToRealOdd::new(len, &mut self.planner)) as Arc> } else { Arc::new(ComplexToRealEven::new(len, &mut self.planner)) as Arc> }; self.c2r_cache.insert(len, Arc::clone(&fft)); fft } } } impl Default for RealFftPlanner { fn default() -> Self { Self::new() } } impl RealToComplexOdd { /// Create a new RealToComplex forward FFT for real-valued input data of a given length, /// and uses the given FftPlanner to build the inner FFT. /// Panics if the length is not odd. pub fn new(length: usize, fft_planner: &mut FftPlanner) -> Self { if length % 2 == 0 { panic!("Length must be odd, got {}", length,); } let fft = fft_planner.plan_fft_forward(length); let scratch_len = fft.get_inplace_scratch_len() + length; RealToComplexOdd { length, fft, scratch_len, } } } impl RealToComplex for RealToComplexOdd { fn process(&self, input: &mut [T], output: &mut [Complex]) -> Res<()> { let mut scratch = self.make_scratch_vec(); self.process_with_scratch(input, output, &mut scratch) } fn process_with_scratch( &self, input: &mut [T], output: &mut [Complex], scratch: &mut [Complex], ) -> Res<()> { if input.len() != self.length { return Err(FftError::InputBuffer(self.length, input.len())); } let expected_output_buffer_size = self.complex_len(); if output.len() != expected_output_buffer_size { return Err(FftError::OutputBuffer( expected_output_buffer_size, output.len(), )); } if scratch.len() < (self.scratch_len) { return Err(FftError::ScratchBuffer(self.scratch_len, scratch.len())); } let (buffer, fft_scratch) = scratch.split_at_mut(self.length); for (val, buf) in input.iter().zip(buffer.iter_mut()) { *buf = Complex::new(*val, T::zero()); } // FFT and store result in buffer_out self.fft.process_with_scratch(buffer, fft_scratch); output.copy_from_slice(&buffer[0..self.complex_len()]); if let Some(elem) = output.first_mut() { elem.im = T::zero(); } Ok(()) } fn get_scratch_len(&self) -> usize { self.scratch_len } fn len(&self) -> usize { self.length } fn make_input_vec(&self) -> Vec { vec![T::zero(); self.len()] } fn make_output_vec(&self) -> Vec> { vec![Complex::zero(); self.complex_len()] } fn make_scratch_vec(&self) -> Vec> { vec![Complex::zero(); self.get_scratch_len()] } } impl RealToComplexEven { /// Create a new RealToComplex forward FFT for real-valued input data of a given length, /// and uses the given FftPlanner to build the inner FFT. /// Panics if the length is not even. pub fn new(length: usize, fft_planner: &mut FftPlanner) -> Self { if length % 2 > 0 { panic!("Length must be even, got {}", length,); } let twiddle_count = if length % 4 == 0 { length / 4 } else { length / 4 + 1 }; let twiddles: Vec> = (1..twiddle_count) .map(|i| compute_twiddle(i, length) * T::from_f64(0.5).unwrap()) .collect(); let fft = fft_planner.plan_fft_forward(length / 2); let scratch_len = fft.get_outofplace_scratch_len(); RealToComplexEven { twiddles, length, fft, scratch_len, } } } impl RealToComplex for RealToComplexEven { fn process(&self, input: &mut [T], output: &mut [Complex]) -> Res<()> { let mut scratch = self.make_scratch_vec(); self.process_with_scratch(input, output, &mut scratch) } fn process_with_scratch( &self, input: &mut [T], output: &mut [Complex], scratch: &mut [Complex], ) -> Res<()> { if input.len() != self.length { return Err(FftError::InputBuffer(self.length, input.len())); } let expected_output_buffer_size = self.complex_len(); if output.len() != expected_output_buffer_size { return Err(FftError::OutputBuffer( expected_output_buffer_size, output.len(), )); } if scratch.len() < (self.scratch_len) { return Err(FftError::ScratchBuffer(self.scratch_len, scratch.len())); } let fftlen = self.length / 2; let buf_in = unsafe { let ptr = input.as_mut_ptr() as *mut Complex; let len = input.len(); std::slice::from_raw_parts_mut(ptr, len / 2) }; // FFT and store result in buffer_out self.fft .process_outofplace_with_scratch(buf_in, &mut output[0..fftlen], scratch); let (mut output_left, mut output_right) = output.split_at_mut(output.len() / 2); // The first and last element don't require any twiddle factors, so skip that work match (output_left.first_mut(), output_right.last_mut()) { (Some(first_element), Some(last_element)) => { // The first and last elements are just a sum and difference of the first value's real and imaginary values let first_value = *first_element; *first_element = Complex { re: first_value.re + first_value.im, im: T::zero(), }; *last_element = Complex { re: first_value.re - first_value.im, im: T::zero(), }; // Chop the first and last element off of our slices so that the loop below doesn't have to deal with them output_left = &mut output_left[1..]; let right_len = output_right.len(); output_right = &mut output_right[..right_len - 1]; } _ => { return Ok(()); } } // Loop over the remaining elements and apply twiddle factors on them for (twiddle, out, out_rev) in zip3( self.twiddles.iter(), output_left.iter_mut(), output_right.iter_mut().rev(), ) { let sum = *out + *out_rev; let diff = *out - *out_rev; let half = T::from_f64(0.5).unwrap(); // Apply twiddle factors. Theoretically we'd have to load 2 separate twiddle factors here, one for the beginning // and one for the end. But the twiddle factor for the end is just the twiddle for the beginning, with the // real part negated. Since it's the same twiddle, we can factor out a ton of math ops and cut the number of // multiplications in half. let twiddled_re_sum = sum * twiddle.re; let twiddled_im_sum = sum * twiddle.im; let twiddled_re_diff = diff * twiddle.re; let twiddled_im_diff = diff * twiddle.im; let half_sum_re = half * sum.re; let half_diff_im = half * diff.im; let output_twiddled_real = twiddled_re_sum.im + twiddled_im_diff.re; let output_twiddled_im = twiddled_im_sum.im - twiddled_re_diff.re; // We finally have all the data we need to write the transformed data back out where we found it. *out = Complex { re: half_sum_re + output_twiddled_real, im: half_diff_im + output_twiddled_im, }; *out_rev = Complex { re: half_sum_re - output_twiddled_real, im: output_twiddled_im - half_diff_im, }; } // If the output len is odd, the loop above can't postprocess the centermost element, so handle that separately. if output.len() % 2 == 1 { if let Some(center_element) = output.get_mut(output.len() / 2) { center_element.im = -center_element.im; } } Ok(()) } fn get_scratch_len(&self) -> usize { self.scratch_len } fn len(&self) -> usize { self.length } fn make_input_vec(&self) -> Vec { vec![T::zero(); self.len()] } fn make_output_vec(&self) -> Vec> { vec![Complex::zero(); self.complex_len()] } fn make_scratch_vec(&self) -> Vec> { vec![Complex::zero(); self.get_scratch_len()] } } impl ComplexToRealOdd { /// Create a new ComplexToRealOdd inverse FFT for complex input spectra. /// The `length` parameter refers to the length of the resulting real-valued signal. /// Uses the given FftPlanner to build the inner FFT. /// Panics if the length is not odd. pub fn new(length: usize, fft_planner: &mut FftPlanner) -> Self { if length % 2 == 0 { panic!("Length must be odd, got {}", length,); } let fft = fft_planner.plan_fft_inverse(length); let scratch_len = length + fft.get_inplace_scratch_len(); ComplexToRealOdd { length, fft, scratch_len, } } } impl ComplexToReal for ComplexToRealOdd { fn process(&self, input: &mut [Complex], output: &mut [T]) -> Res<()> { let mut scratch = self.make_scratch_vec(); self.process_with_scratch(input, output, &mut scratch) } fn process_with_scratch( &self, input: &mut [Complex], output: &mut [T], scratch: &mut [Complex], ) -> Res<()> { let expected_input_buffer_size = self.complex_len(); if input.len() != expected_input_buffer_size { return Err(FftError::InputBuffer( expected_input_buffer_size, input.len(), )); } if output.len() != self.length { return Err(FftError::OutputBuffer(self.length, output.len())); } if scratch.len() < (self.scratch_len) { return Err(FftError::ScratchBuffer(self.scratch_len, scratch.len())); } let first_invalid = if input[0].im != T::from_f64(0.0).unwrap() { input[0].im = T::from_f64(0.0).unwrap(); true } else { false }; let (buffer, fft_scratch) = scratch.split_at_mut(self.length); buffer[0..input.len()].copy_from_slice(input); for (buf, val) in buffer .iter_mut() .rev() .take(self.length / 2) .zip(input.iter().skip(1)) { *buf = val.conj(); } self.fft.process_with_scratch(buffer, fft_scratch); for (val, out) in buffer.iter().zip(output.iter_mut()) { *out = val.re; } if first_invalid { return Err(FftError::InputValues(true, false)); } Ok(()) } fn get_scratch_len(&self) -> usize { self.scratch_len } fn len(&self) -> usize { self.length } fn make_input_vec(&self) -> Vec> { vec![Complex::zero(); self.complex_len()] } fn make_output_vec(&self) -> Vec { vec![T::zero(); self.len()] } fn make_scratch_vec(&self) -> Vec> { vec![Complex::zero(); self.get_scratch_len()] } } impl ComplexToRealEven { /// Create a new ComplexToRealEven inverse FFT for complex input spectra. /// The `length` parameter refers to the length of the resulting real-valued signal. /// Uses the given FftPlanner to build the inner FFT. /// Panics if the length is not even. pub fn new(length: usize, fft_planner: &mut FftPlanner) -> Self { if length % 2 > 0 { panic!("Length must be even, got {}", length,); } let twiddle_count = if length % 4 == 0 { length / 4 } else { length / 4 + 1 }; let twiddles: Vec> = (1..twiddle_count) .map(|i| compute_twiddle(i, length).conj()) .collect(); let fft = fft_planner.plan_fft_inverse(length / 2); let scratch_len = fft.get_outofplace_scratch_len(); ComplexToRealEven { twiddles, length, fft, scratch_len, } } } impl ComplexToReal for ComplexToRealEven { fn process(&self, input: &mut [Complex], output: &mut [T]) -> Res<()> { let mut scratch = self.make_scratch_vec(); self.process_with_scratch(input, output, &mut scratch) } fn process_with_scratch( &self, input: &mut [Complex], output: &mut [T], scratch: &mut [Complex], ) -> Res<()> { let expected_input_buffer_size = self.complex_len(); if input.len() != expected_input_buffer_size { return Err(FftError::InputBuffer( expected_input_buffer_size, input.len(), )); } if output.len() != self.length { return Err(FftError::OutputBuffer(self.length, output.len())); } if scratch.len() < (self.scratch_len) { return Err(FftError::ScratchBuffer(self.scratch_len, scratch.len())); } if input.is_empty() { return Ok(()); } let first_invalid = if input[0].im != T::from_f64(0.0).unwrap() { input[0].im = T::from_f64(0.0).unwrap(); true } else { false }; let last_invalid = if input[input.len() - 1].im != T::from_f64(0.0).unwrap() { input[input.len() - 1].im = T::from_f64(0.0).unwrap(); true } else { false }; let (mut input_left, mut input_right) = input.split_at_mut(input.len() / 2); // We have to preprocess the input in-place before we send it to the FFT. // The first and centermost values have to be preprocessed separately from the rest, so do that now. match (input_left.first_mut(), input_right.last_mut()) { (Some(first_input), Some(last_input)) => { let first_sum = *first_input + *last_input; let first_diff = *first_input - *last_input; *first_input = Complex { re: first_sum.re - first_sum.im, im: first_diff.re - first_diff.im, }; input_left = &mut input_left[1..]; let right_len = input_right.len(); input_right = &mut input_right[..right_len - 1]; } _ => return Ok(()), }; // now, in a loop, preprocess the rest of the elements 2 at a time. for (twiddle, fft_input, fft_input_rev) in zip3( self.twiddles.iter(), input_left.iter_mut(), input_right.iter_mut().rev(), ) { let sum = *fft_input + *fft_input_rev; let diff = *fft_input - *fft_input_rev; // Apply twiddle factors. Theoretically we'd have to load 2 separate twiddle factors here, one for the beginning // and one for the end. But the twiddle factor for the end is just the twiddle for the beginning, with the // real part negated. Since it's the same twiddle, we can factor out a ton of math ops and cut the number of // multiplications in half. let twiddled_re_sum = sum * twiddle.re; let twiddled_im_sum = sum * twiddle.im; let twiddled_re_diff = diff * twiddle.re; let twiddled_im_diff = diff * twiddle.im; let output_twiddled_real = twiddled_re_sum.im + twiddled_im_diff.re; let output_twiddled_im = twiddled_im_sum.im - twiddled_re_diff.re; // We finally have all the data we need to write our preprocessed data back where we got it from. *fft_input = Complex { re: sum.re - output_twiddled_real, im: diff.im - output_twiddled_im, }; *fft_input_rev = Complex { re: sum.re + output_twiddled_real, im: -output_twiddled_im - diff.im, } } // If the output len is odd, the loop above can't preprocess the centermost element, so handle that separately if input.len() % 2 == 1 { let center_element = input[input.len() / 2]; let doubled = center_element + center_element; input[input.len() / 2] = doubled.conj(); } // FFT and store result in buffer_out let buf_out = unsafe { let ptr = output.as_mut_ptr() as *mut Complex; let len = output.len(); std::slice::from_raw_parts_mut(ptr, len / 2) }; self.fft .process_outofplace_with_scratch(&mut input[..buf_out.len()], buf_out, scratch); if first_invalid || last_invalid { return Err(FftError::InputValues(first_invalid, last_invalid)); } Ok(()) } fn get_scratch_len(&self) -> usize { self.scratch_len } fn len(&self) -> usize { self.length } fn make_input_vec(&self) -> Vec> { vec![Complex::zero(); self.complex_len()] } fn make_output_vec(&self) -> Vec { vec![T::zero(); self.len()] } fn make_scratch_vec(&self) -> Vec> { vec![Complex::zero(); self.get_scratch_len()] } } #[cfg(test)] mod tests { use crate::FftError; use crate::RealFftPlanner; use rand::Rng; use rustfft::num_complex::Complex; use rustfft::num_traits::{Float, Zero}; use rustfft::FftPlanner; use std::error::Error; use std::ops::Sub; // get the largest difference fn compare_complex(a: &[Complex], b: &[Complex]) -> T { a.iter() .zip(b.iter()) .fold(T::zero(), |maxdiff, (val_a, val_b)| { let diff = (val_a - val_b).norm(); if maxdiff > diff { maxdiff } else { diff } }) } // get the largest difference fn compare_scalars(a: &[T], b: &[T]) -> T { a.iter() .zip(b.iter()) .fold(T::zero(), |maxdiff, (val_a, val_b)| { let diff = (*val_a - *val_b).abs(); if maxdiff > diff { maxdiff } else { diff } }) } // Compare ComplexToReal with standard inverse FFT #[test] fn complex_to_real_64() { for length in 1..1000 { let mut real_planner = RealFftPlanner::::new(); let c2r = real_planner.plan_fft_inverse(length); let mut out_a = c2r.make_output_vec(); let mut indata = c2r.make_input_vec(); let mut rustfft_check: Vec> = vec![Complex::zero(); length]; let mut rng = rand::rng(); for val in indata.iter_mut() { *val = Complex::new(rng.random::(), rng.random::()); } indata[0].im = 0.0; if length % 2 == 0 { indata[length / 2].im = 0.0; } for (val_long, val) in rustfft_check .iter_mut() .take(c2r.complex_len()) .zip(indata.iter()) { *val_long = *val; } for (val_long, val) in rustfft_check .iter_mut() .rev() .take(length / 2) .zip(indata.iter().skip(1)) { *val_long = val.conj(); } let mut fft_planner = FftPlanner::::new(); let fft = fft_planner.plan_fft_inverse(length); c2r.process(&mut indata, &mut out_a).unwrap(); fft.process(&mut rustfft_check); let check_real = rustfft_check.iter().map(|val| val.re).collect::>(); let maxdiff = compare_scalars(&out_a, &check_real); assert!( maxdiff < 1.0e-9, "Length: {}, too large error: {}", length, maxdiff ); } } // Compare ComplexToReal with standard inverse FFT #[test] fn complex_to_real_32() { for length in 1..1000 { let mut real_planner = RealFftPlanner::::new(); let c2r = real_planner.plan_fft_inverse(length); let mut out_a = c2r.make_output_vec(); let mut indata = c2r.make_input_vec(); let mut rustfft_check: Vec> = vec![Complex::zero(); length]; let mut rng = rand::rng(); for val in indata.iter_mut() { *val = Complex::new(rng.random::(), rng.random::()); } indata[0].im = 0.0; if length % 2 == 0 { indata[length / 2].im = 0.0; } for (val_long, val) in rustfft_check .iter_mut() .take(c2r.complex_len()) .zip(indata.iter()) { *val_long = *val; } for (val_long, val) in rustfft_check .iter_mut() .rev() .take(length / 2) .zip(indata.iter().skip(1)) { *val_long = val.conj(); } let mut fft_planner = FftPlanner::::new(); let fft = fft_planner.plan_fft_inverse(length); c2r.process(&mut indata, &mut out_a).unwrap(); fft.process(&mut rustfft_check); let check_real = rustfft_check.iter().map(|val| val.re).collect::>(); let maxdiff = compare_scalars(&out_a, &check_real); assert!( maxdiff < 5.0e-4, "Length: {}, too large error: {}", length, maxdiff ); } } // Test that ComplexToReal returns the right errors #[test] fn complex_to_real_errors_even() { let length = 100; let mut real_planner = RealFftPlanner::::new(); let c2r = real_planner.plan_fft_inverse(length); let mut out_a = c2r.make_output_vec(); let mut indata = c2r.make_input_vec(); let mut rng = rand::rng(); // Make some valid data for val in indata.iter_mut() { *val = Complex::new(rng.random::(), rng.random::()); } indata[0].im = 0.0; indata[50].im = 0.0; // this should be ok assert!(c2r.process(&mut indata, &mut out_a).is_ok()); // Make some invalid data, first point invalid for val in indata.iter_mut() { *val = Complex::new(rng.random::(), rng.random::()); } indata[50].im = 0.0; let res = c2r.process(&mut indata, &mut out_a); assert!(res.is_err()); assert!(matches!(res, Err(FftError::InputValues(true, false)))); // Make some invalid data, last point invalid for val in indata.iter_mut() { *val = Complex::new(rng.random::(), rng.random::()); } indata[0].im = 0.0; let res = c2r.process(&mut indata, &mut out_a); assert!(res.is_err()); assert!(matches!(res, Err(FftError::InputValues(false, true)))); } // Test that ComplexToReal returns the right errors #[test] fn complex_to_real_errors_odd() { let length = 101; let mut real_planner = RealFftPlanner::::new(); let c2r = real_planner.plan_fft_inverse(length); let mut out_a = c2r.make_output_vec(); let mut indata = c2r.make_input_vec(); let mut rng = rand::rng(); // Make some valid data for val in indata.iter_mut() { *val = Complex::new(rng.random::(), rng.random::()); } indata[0].im = 0.0; // this should be ok assert!(c2r.process(&mut indata, &mut out_a).is_ok()); // Make some invalid data, first point invalid for val in indata.iter_mut() { *val = Complex::new(rng.random::(), rng.random::()); } let res = c2r.process(&mut indata, &mut out_a); assert!(res.is_err()); assert!(matches!(res, Err(FftError::InputValues(true, false)))); } // Compare RealToComplex with standard FFT #[test] fn real_to_complex_64() { for length in 1..1000 { let mut real_planner = RealFftPlanner::::new(); let r2c = real_planner.plan_fft_forward(length); let mut out_a = r2c.make_output_vec(); let mut indata = r2c.make_input_vec(); let mut rng = rand::rng(); for val in indata.iter_mut() { *val = rng.random::(); } let mut rustfft_check = indata .iter() .map(Complex::from) .collect::>>(); let mut fft_planner = FftPlanner::::new(); let fft = fft_planner.plan_fft_forward(length); fft.process(&mut rustfft_check); r2c.process(&mut indata, &mut out_a).unwrap(); assert_eq!(out_a[0].im, 0.0, "First imaginary component must be zero"); if length % 2 == 0 { assert_eq!( out_a.last().unwrap().im, 0.0, "Last imaginary component for even lengths must be zero" ); } let maxdiff = compare_complex(&out_a, &rustfft_check[0..r2c.complex_len()]); assert!( maxdiff < 1.0e-9, "Length: {}, too large error: {}", length, maxdiff ); } } // Compare RealToComplex with standard FFT #[test] fn real_to_complex_32() { for length in 1..1000 { let mut real_planner = RealFftPlanner::::new(); let r2c = real_planner.plan_fft_forward(length); let mut out_a = r2c.make_output_vec(); let mut indata = r2c.make_input_vec(); let mut rng = rand::rng(); for val in indata.iter_mut() { *val = rng.random::(); } let mut rustfft_check = indata .iter() .map(Complex::from) .collect::>>(); let mut fft_planner = FftPlanner::::new(); let fft = fft_planner.plan_fft_forward(length); fft.process(&mut rustfft_check); r2c.process(&mut indata, &mut out_a).unwrap(); assert_eq!(out_a[0].im, 0.0, "First imaginary component must be zero"); if length % 2 == 0 { assert_eq!( out_a.last().unwrap().im, 0.0, "Last imaginary component for even lengths must be zero" ); } let maxdiff = compare_complex(&out_a, &rustfft_check[0..r2c.complex_len()]); assert!( maxdiff < 5.0e-4, "Length: {}, too large error: {}", length, maxdiff ); } } // Check that the ? operator works on the custom errors. No need to run, just needs to compile. #[allow(dead_code)] fn test_error() -> Result<(), Box> { let mut real_planner = RealFftPlanner::::new(); let r2c = real_planner.plan_fft_forward(100); let mut out_a = r2c.make_output_vec(); let mut indata = r2c.make_input_vec(); r2c.process(&mut indata, &mut out_a)?; Ok(()) } }