realfft-3.4.0/.cargo_vcs_info.json0000644000000001360000000000100124630ustar { "git": { "sha1": "b006cb1c61a6df93610df7a2b2894ace79728b48" }, "path_in_vcs": "" }realfft-3.4.0/.github/workflows/publish.yml000064400000000000000000000007551046102023000170500ustar 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.4.0/.github/workflows/run_tests.yml000064400000000000000000000024231046102023000174220ustar 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.4.0/.gitignore000064400000000000000000000000231046102023000132360ustar 00000000000000/target Cargo.lock realfft-3.4.0/Cargo.lock0000644000000471160000000000100104470ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags", "textwrap", "unicode-width", ] [[package]] name = "criterion" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", "clap", "criterion-plot", "csv", "itertools", "lazy_static", "num-traits", "oorandom", "plotters", "rayon", "regex", "serde", "serde_cbor", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-deque" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 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.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "csv" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" dependencies = [ "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" dependencies = [ "memchr", ] [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "half" version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[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.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[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.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 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.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 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.4.0" dependencies = [ "criterion", "rand", "rustfft", ] [[package]] name = "regex" version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rustfft" version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43806561bc506d0c5d160643ad742e3161049ac01027b5e6d7524091fd401d86" dependencies = [ "num-complex", "num-integer", "num-traits", "primal-check", "strength_reduce", "transpose", "version_check", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[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.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_cbor" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", "serde", ] [[package]] name = "serde_derive" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 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.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[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.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[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.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[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 = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[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 = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] realfft-3.4.0/Cargo.toml0000644000000024720000000000100104660ustar # 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.4.0" authors = ["HEnquist "] build = 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" [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.2.0" [dev-dependencies.criterion] version = "0.3" [dev-dependencies.rand] version = "0.8.5" realfft-3.4.0/Cargo.toml.orig000064400000000000000000000012161046102023000141420ustar 00000000000000[package] name = "realfft" version = "3.4.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 [dependencies] rustfft = "6.2.0" [dev-dependencies] criterion = "0.3" rand = "0.8.5" [[bench]] name = "realfft" harness = false realfft-3.4.0/README.md000064400000000000000000000135331046102023000125370ustar 00000000000000# realfft ## 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. 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: ``` 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: ``` 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: ``` 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: ``` 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: ``` 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: ``` RealFFT(x) = [(X0r, 0), (X1r, X1i), (X2r, X2i), (X3r, 0)] ``` The result for 7 elements is: ``` 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: ``` cargo doc --open ``` ### Benchmarks To run a set of benchmarks comparing real-to-complex FFT with standard complex-to-complex, type: ``` 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(); ``` #### Versions - 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.4.0/benches/realfft.rs000064400000000000000000000102451046102023000146550ustar 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.4.0/examples/concurrency.rs000064400000000000000000000012471046102023000157750ustar 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.4.0/src/lib.rs000064400000000000000000001303071046102023000131620ustar 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. //! //! 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 a forward and an inverse FFT 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: //! ```text //! cargo doc --open //! ``` //! //! ## Benchmarks //! //! To run a set of benchmarks comparing real-to-complex FFT with standard complex-to-complex, type: //! ```text //! 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. //! ``` //! 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(); //! ``` //! //! ### Versions //! - 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. 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::thread_rng(); for val in indata.iter_mut() { *val = Complex::new(rng.gen::(), rng.gen::()); } 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::thread_rng(); for val in indata.iter_mut() { *val = Complex::new(rng.gen::(), rng.gen::()); } 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::thread_rng(); // Make some valid data for val in indata.iter_mut() { *val = Complex::new(rng.gen::(), rng.gen::()); } 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.gen::(), rng.gen::()); } 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.gen::(), rng.gen::()); } 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::thread_rng(); // Make some valid data for val in indata.iter_mut() { *val = Complex::new(rng.gen::(), rng.gen::()); } 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.gen::(), rng.gen::()); } 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::thread_rng(); for val in indata.iter_mut() { *val = rng.gen::(); } 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::thread_rng(); for val in indata.iter_mut() { *val = rng.gen::(); } 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(()) } }