rubato-0.16.2/.cargo_vcs_info.json0000644000000001360000000000100124160ustar { "git": { "sha1": "c20dfb7fa80fa43d0781312be67efe81f2607219" }, "path_in_vcs": "" }rubato-0.16.2/.github/workflows/ci_test.yml000064400000000000000000000043111046102023000167570ustar 00000000000000on: [push, pull_request] name: CI test and lint jobs: check_test: name: Check and test 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 check run: cargo check - name: Run cargo check no features run: cargo check --no-default-features - name: Run cargo test run: cargo test lints: 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 check_test_aarch64: name: Check and test Linux arm 64bit runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: targets: aarch64-unknown-linux-gnu - name: Install cross run: cargo install cross --git https://github.com/cross-rs/cross - name: Run cargo check on arm run: cross check --target aarch64-unknown-linux-gnu - name: Run cargo test on arm run: cross test --target aarch64-unknown-linux-gnu check_wasm32: name: Check wasm32 runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: targets: wasm32-wasip1 - name: Run cargo check on wasm32 run: cargo check --target=wasm32-wasip1 test_32bit: name: Test 32-bit runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: targets: i686-unknown-linux-gnu - name: Run cargo test on 32-bit run: cargo test target=i686-unknown-linux-gnu rubato-0.16.2/.github/workflows/publish.yml000064400000000000000000000007651046102023000170040ustar 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 }} rubato-0.16.2/.gitignore000064400000000000000000000000311046102023000131700ustar 00000000000000/target Cargo.lock *.raw rubato-0.16.2/Cargo.lock0000644000000655040000000000100104030ustar # 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 = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", "windows-sys", ] [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[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 = "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.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "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", ] [[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 = "env_filter" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", ] [[package]] name = "env_logger" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", "log", "regex", "termcolor", ] [[package]] name = "env_logger" version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" dependencies = [ "anstream", "anstyle", "env_filter", "log", ] [[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 = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hermit-abi" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" [[package]] name = "humantime" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] name = "is-terminal" version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", "windows-sys", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[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.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 = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ "regex-automata 0.1.10", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", "winapi", ] [[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 = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[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.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 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 = "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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390252372b7f2aac8360fc5e72eba10136b166d6faeed97e6d0c8324eb99b2b1" dependencies = [ "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 0.4.9", "regex-syntax 0.8.5", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ "regex-syntax 0.6.29", ] [[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 0.8.5", ] [[package]] name = "regex-syntax" version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rubato" version = "0.16.2" dependencies = [ "approx", "criterion", "env_logger 0.10.2", "log", "num-complex", "num-integer", "num-traits", "rand", "realfft", "test-log", ] [[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 = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[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 = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[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.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "test-log" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f46083d221181166e5b6f6b1e5f1d499f3a76888826e6cb1d057554157cd0f" dependencies = [ "env_logger 0.11.7", "test-log-macros", "tracing-subscriber", ] [[package]] name = "test-log-macros" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] [[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 = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "sharded-slab", "thread_local", "tracing", "tracing-core", "tracing-log", ] [[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 = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[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.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" 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.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", "syn", ] rubato-0.16.2/Cargo.toml0000644000000037370000000000100104260ustar # 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 = "2021" rust-version = "1.61" name = "rubato" version = "0.16.2" authors = ["HEnquist "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Asynchronous resampling library intended for audio data" readme = "README.md" keywords = [ "interpolation", "resampling", ] categories = ["multimedia::audio"] license = "MIT" repository = "https://github.com/HEnquist/rubato" [features] default = ["fft_resampler"] fft_resampler = [ "realfft", "num-complex", ] log = ["dep:log"] [lib] name = "rubato" path = "src/lib.rs" bench = false [[example]] name = "fastfixedin_ramp64" path = "examples/fastfixedin_ramp64.rs" [[example]] name = "fixedout_ramp64" path = "examples/fixedout_ramp64.rs" [[example]] name = "process_f64" path = "examples/process_f64.rs" [[bench]] name = "resamplers" path = "benches/resamplers.rs" harness = false [dependencies.log] version = "0.4.18" optional = true [dependencies.num-complex] version = "0.4" optional = true [dependencies.num-integer] version = "0.1.45" [dependencies.num-traits] version = "0.2" [dependencies.realfft] version = "3.3.0" optional = true [dev-dependencies.approx] version = "0.5.1" [dev-dependencies.criterion] version = "0.5.1" [dev-dependencies.env_logger] version = "0.10.0" [dev-dependencies.log] version = "0.4.18" [dev-dependencies.num-traits] version = "0.2.15" [dev-dependencies.rand] version = "0.8.5" [dev-dependencies.test-log] version = "0.2.16" rubato-0.16.2/Cargo.toml.orig000064400000000000000000000017661046102023000141070ustar 00000000000000[package] name = "rubato" version = "0.16.2" rust-version = "1.61" authors = ["HEnquist "] description = "Asynchronous resampling library intended for audio data" license = "MIT" repository = "https://github.com/HEnquist/rubato" keywords = ["interpolation", "resampling"] categories = ["multimedia::audio"] readme = "README.md" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] default = ["fft_resampler"] fft_resampler = ["realfft", "num-complex"] log = ["dep:log"] [dependencies] log = { version = "0.4.18", optional = true } realfft = { version = "3.3.0", optional = true } num-complex = { version = "0.4", optional = true } num-integer = "0.1.45" num-traits = "0.2" [dev-dependencies] env_logger = "0.10.0" criterion = "0.5.1" rand = "0.8.5" num-traits = "0.2.15" log = "0.4.18" approx = "0.5.1" test-log = "0.2.16" [[bench]] name = "resamplers" harness = false [lib] bench = false path = "src/lib.rs" rubato-0.16.2/LICENSE.txt000064400000000000000000000020411046102023000130260ustar 00000000000000Copyright (c) 2020 Henrik Enquist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.rubato-0.16.2/README.md000064400000000000000000000307571046102023000125010ustar 00000000000000# Rubato An audio sample rate conversion library for Rust. This library provides resamplers to process audio in chunks. The ratio between input and output sample rates is completely free. Implementations are available that accept a fixed length input while returning a variable length output, and vice versa. Rubato can be used in realtime applications without any allocation during processing by preallocating a [Resampler] and using its [input_buffer_allocate](Resampler::input_buffer_allocate) and [output_buffer_allocate](Resampler::output_buffer_allocate) methods before beginning processing. The [log feature](#log-enable-logging) feature should be disabled for realtime use (it is disabled by default). ## Input and output data format Input and output data are stored in a non-interleaved format. Input and output data are stored as slices of references, `&[AsRef<[f32]>]` or `&[AsRef<[f64]>]`. The inner references (`AsRef<[f32]>` or `AsRef<[f64]>`) hold the sample values for one channel each. Since normal vectors implement the `AsRef` trait, `Vec>` and `Vec>` can be used for both input and output. ## Asynchronous resampling The asynchronous resamplers are available with and without anti-aliasing filters. Resampling with anti-aliasing is based on band-limited interpolation using sinc interpolation filters. The sinc interpolation upsamples by an adjustable factor, and then the new sample points are calculated by interpolating between these points. The resampling ratio can be updated at any time. Resampling without anti-aliasing omits the cpu-heavy sinc interpolation. This runs much faster but produces a lower quality result. ## Synchronous resampling Synchronous resampling is implemented via FFT. The data is FFT:ed, the spectrum modified, and then inverse FFT:ed to get the resampled data. This type of resampler is considerably faster but doesn't support changing the resampling ratio. ## Usage The resamplers provided by this library are intended to process audio in chunks. The optimal chunk size is determined by the application, but will likely end up somwhere between a few hundred to a few thousand frames. This gives a good compromize between efficiency and memory usage. ### Real time considerations Rubato is suitable for real-time applications when using the `Resampler::process_into_buffer()` method. This stores the output in a pre-allocated output buffer, and performs no allocations or other operations that may block the thread. ### Resampling a given audio clip A suggested simple process for resampling an audio clip of known length to a new sample rate is as follows. Here it is assumed that the source data is stored in a vec, or some other structure that supports reading arbitrary number of frames at a time. For simplicity, the output is stored in a temporary buffer during resampling, and copied to the destination afterwards. Preparations: 1. Create a resampler of suitable type, for example FFTFixedIn which is quite fast and gives good quality. Since neither input or output has any restrictions for the number of frames that can be read or written at a time, the chunk size can be chosen arbitrarily. Start with a chunk size of for example 1024. 2. Create an input buffer. 3. Create a temporary buffer for collecting the resampled output data. 4. Call `Resampler::output_delay()` to know how many frames of delay the resampler gives. Store the number as `delay`. 5. Calculate the new clip length as `new_length = original_length * new_rate / original_rate`. Now it's time to process the bulk of the clip by repeated procesing calls. Loop: 1. Call `Resampler::input_frames_next()` to learn how many frames the resampler needs. 2. Check the number of available frames in the source. If it is less than the needed input size, break the loop. 3. Read the required number of frames from the source, convert the sample values to float, and copy them to the input buffer. 4. Call `Resampler::process()` or `Resampler::process_into_buffer()`. 5. Append the output frames to the temporary output buffer. The next step is to process the last remaining frames. 1. Read the available frames fom the source, convert the sample values to float, and copy them to the input buffer. 2. Call `Resampler::process_partial()` or `Resampler::process_partial_into_buffer()`. 3. Append the output frames to the temporary buffer. At this point, all frames have been sent to the resampler, but because of the delay through the resampler, it may still have some frames in its internal buffers. When all wanted frames have been generated, the length of the temporary output buffer should be at least `new_length + delay`. If this is not the case, call `Resampler::process_partial()` or `Resampler::process_partial_into_buffer()` with `None` as input, and append the output to the temporary output buffer. If needed, repeat until the length is sufficient. Finally, copy the data from the temporary output buffer to the desired destination. Skip the first `delay` frames, and copy `new_length` frames. If there is more than one clip to resample from and to the same sample rates, the same resampler should be reused. Creating a new resampler is an expensive task and should be avoided if possible. Start the procedire from the start, but instead of creating a new resampler, call `Resampler::reset()` on the existing one to prepare it for a new job. ### Resampling a stream When resamping a stream, the process is normally performed in real time, and either the input of output is some API that provides or consumes frames at a given rate. #### Example, record to file from an audio API Audio APIs such as [CoreAudio](https://crates.io/crates/coreaudio-rs) on MacOS, or the cross platform [cpal](https://crates.io/crates/cpal) crate, often use callback functions for data exchange. A complete When capturing audio from these, the application passes a function to the audio API. The API then calls this function periodically, with a pointer to a data buffer containing new audio frames. The data buffer size is usually the same on every call, but that varies between APIs. It is important that the function does not block, since this would block some internal loop of the API and cause loss of some audio data. It is recommended to keep the callback function light. Ideally it should read the provided audio data from the buffer provided by the API, and optionally perform some light processing such as sample format conversion. No heavy processing such as resampling should be performed here. It should then store the audio data to a shared buffer. The buffer may be a `Arc>>`, or something more advanced such as [ringbuf](https://crates.io/crates/ringbuf). A separate loop, running either in the main or a separate thread, should then read from that buffer, resample, and save to file. If the Audio API provides a fixed buffer size, then this number of frames is a good choice for the resampler chunk size. If the size varies, the shared buffer can be used to adapt the chunk sizes of the audio API and the resampler. A good starting point for the resampler chunk size is to use an "easy" value near the average chunk size of the audio API. Make sure that the shared buffer is large enough to not get full in case for the loop gets blocked waiting for example for disk access. The loop should follow a process similar to [resampling a clip](#resampling-a-given-audio-clip), but the input is now the shared buffer. The loop needs to wait for the needed number of frames to become available in the buffer, before reading and passing them to the resampler. It would also be appropriate to omit the temporary output buffer, and write the output directly to the destination. The [hound](https://crates.io/crates/hound) crate is a popular choice for reading and writing uncompressed audio formats. ## SIMD acceleration ### Asynchronous resampling with anti-aliasing The asynchronous resampler supports SIMD on x86_64 and on aarch64. The SIMD capabilities of the CPU are determined at runtime. If no supported SIMD instruction set is available, it falls back to a scalar implementation. On x86_64, it will try to use AVX. If AVX isn't available, it will instead try SSE3. On aarch64 (64-bit Arm), it will use Neon if available. ### Synchronous resampling The synchronous resamplers benefit from the SIMD support of the RustFFT library. ## Cargo features ### `fft_resampler`: Enable the FFT based synchronous resamplers This feature is enabled by default. Disable it if the FFT resamplers are not needed, to save compile time and reduce the resulting binary size. ### `log`: Enable logging This feature enables logging via the `log` crate. This is intended for debugging purposes. Note that outputting logs allocates a [std::string::String] and most logging implementations involve various other system calls. These calls may take some (unpredictable) time to return, during which the application is blocked. This means that logging should be avoided if using this library in a realtime application. The `log` feature can be enabled when running tests, which can be very useful when debugging. The logging level can be set via the `RUST_LOG` environment variable. Example: ```sh RUST_LOG=trace cargo test --features log ``` ## Example Resample a single chunk of a dummy audio file from 44100 to 48000 Hz. See also the "process_f64" example that can be used to process a file from disk. ```rust use rubato::{Resampler, SincFixedIn, SincInterpolationType, SincInterpolationParameters, WindowFunction}; let params = SincInterpolationParameters { sinc_len: 256, f_cutoff: 0.95, interpolation: SincInterpolationType::Linear, oversampling_factor: 256, window: WindowFunction::BlackmanHarris2, }; let mut resampler = SincFixedIn::::new( 48000 as f64 / 44100 as f64, 2.0, params, 1024, 2, ).unwrap(); let waves_in = vec![vec![0.0f64; 1024];2]; let waves_out = resampler.process(&waves_in, None).unwrap(); ``` ## Included examples The `examples` directory contains a few sample applications for testing the resamplers. There are also Python scripts for generating simple test signals as well as analyzing the resampled results. The examples read and write raw audio data in 64-bit float format. They can be used to process .wav files if the files are first converted to the right format. Use `sox` to convert a .wav to raw samples: ```sh sox some_file.wav -e floating-point -b 64 some_file_f64.raw ``` After processing, the result can be converted back to new .wav. This examples converts to 16-bits at 44.1 kHz: ```sh sox -e floating-point -b 64 -r 44100 -c 2 resampler_output.raw -e signed-integer -b 16 some_file_resampled.wav ``` Many audio editors, for example Audacity, are also able to directly import and export the raw samples. ## Compatibility The `rubato` crate requires rustc version 1.61 or newer. ## Changelog - v0.16.2 - Fix issues when using on 32-bit systems. - v0.16.1 - Fix issue in test suite when building without FFT resamplers. - v0.16.0 - Add support for changing the fixed input or output size of the asynchronous resamplers. - v0.15.0 - Make FFT resamplers optional via `fft_resampler` feature. - Fix calculation of input and output sizes when creating FftFixedInOut resampler. - Fix panic when using very small chunksizes (less than 5). - v0.14.1 - More bugfixes for buffer allocation and max output length calculation. - Fix building with `log` feature. - v0.14.0 - Add argument to let `input/output_buffer_allocate()` optionally pre-fill buffers with zeros. - Add convenience methods for managing buffers. - Bugfixes for buffer allocation and max output length calculation. - v0.13.0 - Switch to slices of references for input and output data. - Add faster (lower quality) asynchronous resamplers. - Add a macro to help implement custom object safe resamplers. - Optional smooth ramping of ratio changes to avoid audible steps. - Add convenience methods for handling last frames in a stream. - Add resampler reset method. - Refactoring for a more logical structure. - Add helper function for calculating cutoff frequency. - Add quadratic interpolation for sinc resampler. - Add method to get the delay through a resampler as a number of output frames. - v0.12.0 - Always enable all simd acceleration (and remove the simd Cargo features). - v0.11.0 - New api to allow use in realtime applications. - Configurable adjust range of asynchronous resamplers. - v0.10.1 - Fix compiling with neon feature after changes in latest nightly. - v0.10.0 - Add an object-safe wrapper trait for Resampler. - v0.9.0 - Accept any AsRef<\[T\]> as input. License: MIT rubato-0.16.2/benches/resamplers.rs000064400000000000000000000267321046102023000153520ustar 00000000000000use criterion::{black_box, criterion_group, criterion_main, Criterion}; extern crate rubato; use rubato::sinc_interpolator::ScalarInterpolator; #[cfg(target_arch = "x86_64")] use rubato::sinc_interpolator::sinc_interpolator_avx::AvxInterpolator; #[cfg(target_arch = "aarch64")] use rubato::sinc_interpolator::sinc_interpolator_neon::NeonInterpolator; #[cfg(target_arch = "x86_64")] use rubato::sinc_interpolator::sinc_interpolator_sse::SseInterpolator; #[cfg(feature = "fft_resampler")] use rubato::FftFixedIn; use rubato::{ FastFixedIn, PolynomialDegree, Resampler, SincFixedIn, SincInterpolationType, WindowFunction, }; #[cfg(feature = "fft_resampler")] fn bench_fftfixedin(c: &mut Criterion) { let chunksize = 1024; let mut resampler = FftFixedIn::::new(44100, 192000, 1024, 2, 1).unwrap(); let waveform = vec![vec![0.0 as f64; chunksize]; 1]; c.bench_function("FftFixedIn f64", |b| { b.iter(|| resampler.process(black_box(&waveform), None).unwrap()) }); } #[cfg(feature = "fft_resampler")] fn bench_fftfixedin_32(c: &mut Criterion) { let chunksize = 1024; let mut resampler = FftFixedIn::::new(44100, 192000, 1024, 2, 1).unwrap(); let waveform = vec![vec![0.0 as f32; chunksize]; 1]; c.bench_function("FftFixedIn f32", |b| { b.iter(|| resampler.process(black_box(&waveform), None).unwrap()) }); } /// Helper to unwrap the constructed interpolator if appropriate. macro_rules! unwrap_helper { (infallible $var:ident) => { $var }; ($var:ident) => { $var.unwrap() }; } macro_rules! bench_async_resampler { ($ft:ty, $it:ident, $ip:expr, $f:ident, $desc:literal $(, $unwrap:tt)?) => { fn $f(c: &mut Criterion) { let chunksize = 1024; let sinc_len = 256; let f_cutoff = 0.9473371669037001; let oversampling_factor = 256; let window = WindowFunction::BlackmanHarris2; let resample_ratio = 192000 as f64 / 44100 as f64; let interpolation_type = $ip; let interpolator = $it::<$ft>::new( sinc_len, oversampling_factor, f_cutoff, window, ); let interpolator = unwrap_helper!($($unwrap)* interpolator); let interpolator = Box::new(interpolator); let mut resampler = SincFixedIn::<$ft>::new_with_interpolator( resample_ratio, 1.1, interpolation_type, interpolator, chunksize, 1, ).unwrap(); let waveform = vec![vec![0.0 as $ft; chunksize]; 1]; c.bench_function($desc, |b| b.iter(|| resampler.process(black_box(&waveform), None).unwrap())); } }; } bench_async_resampler!( f32, ScalarInterpolator, SincInterpolationType::Cubic, bench_scalar_async_cubic_32, "scalar async cubic 32", infallible ); bench_async_resampler!( f32, ScalarInterpolator, SincInterpolationType::Linear, bench_scalar_async_linear_32, "scalar async linear 32", infallible ); bench_async_resampler!( f32, ScalarInterpolator, SincInterpolationType::Nearest, bench_scalar_async_nearest_32, "scalar async nearest 32", infallible ); bench_async_resampler!( f64, ScalarInterpolator, SincInterpolationType::Cubic, bench_scalar_async_cubic_64, "scalar async cubic 64", infallible ); bench_async_resampler!( f64, ScalarInterpolator, SincInterpolationType::Linear, bench_scalar_async_linear_64, "scalar async linear 64", infallible ); bench_async_resampler!( f64, ScalarInterpolator, SincInterpolationType::Nearest, bench_scalar_async_nearest_64, "scalar async nearest 64", infallible ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f32, SseInterpolator, SincInterpolationType::Cubic, bench_sse_async_cubic_32, "sse async cubic 32" ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f32, SseInterpolator, SincInterpolationType::Linear, bench_sse_async_linear_32, "sse async linear 32" ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f32, SseInterpolator, SincInterpolationType::Nearest, bench_sse_async_nearest_32, "sse async nearest 32" ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f64, SseInterpolator, SincInterpolationType::Cubic, bench_sse_async_cubic_64, "sse async cubic 64" ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f64, SseInterpolator, SincInterpolationType::Linear, bench_sse_async_linear_64, "sse async linear 64" ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f64, SseInterpolator, SincInterpolationType::Nearest, bench_sse_async_nearest_64, "sse async nearest 64" ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f32, AvxInterpolator, SincInterpolationType::Cubic, bench_avx_async_cubic_32, "avx async cubic 32" ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f32, AvxInterpolator, SincInterpolationType::Linear, bench_avx_async_linear_32, "avx async linear 32" ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f32, AvxInterpolator, SincInterpolationType::Nearest, bench_avx_async_nearest_32, "avx async nearest 32" ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f64, AvxInterpolator, SincInterpolationType::Cubic, bench_avx_async_cubic_64, "avx async cubic 64" ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f64, AvxInterpolator, SincInterpolationType::Linear, bench_avx_async_linear_64, "avx async linear 64" ); #[cfg(target_arch = "x86_64")] bench_async_resampler!( f64, AvxInterpolator, SincInterpolationType::Nearest, bench_avx_async_nearest_64, "avx async nearest 64" ); #[cfg(target_arch = "aarch64")] bench_async_resampler!( f32, NeonInterpolator, SincInterpolationType::Cubic, bench_neon_async_cubic_32, "neon async cubic 32" ); #[cfg(target_arch = "aarch64")] bench_async_resampler!( f32, NeonInterpolator, SincInterpolationType::Linear, bench_neon_async_linear_32, "neon async linear 32" ); #[cfg(target_arch = "aarch64")] bench_async_resampler!( f32, NeonInterpolator, SincInterpolationType::Nearest, bench_neon_async_nearest_32, "neon async nearest 32" ); #[cfg(target_arch = "aarch64")] bench_async_resampler!( f64, NeonInterpolator, SincInterpolationType::Cubic, bench_neon_async_cubic_64, "neon async cubic 64" ); #[cfg(target_arch = "aarch64")] bench_async_resampler!( f64, NeonInterpolator, SincInterpolationType::Linear, bench_neon_async_linear_64, "neon async linear 64" ); #[cfg(target_arch = "aarch64")] bench_async_resampler!( f64, NeonInterpolator, SincInterpolationType::Nearest, bench_neon_async_nearest_64, "neon async nearest 64" ); macro_rules! bench_fast_async_resampler { ($ft:ty, $ip:expr, $f:ident, $desc:literal) => { fn $f(c: &mut Criterion) { let chunksize = 1024; let interpolation_type = $ip; let resample_ratio = 192000 as f64 / 44100 as f64; let mut resampler = FastFixedIn::<$ft>::new(resample_ratio, 1.1, interpolation_type, chunksize, 1) .unwrap(); let waveform = vec![vec![0.0 as $ft; chunksize]; 1]; c.bench_function($desc, |b| { b.iter(|| resampler.process(black_box(&waveform), None).unwrap()) }); } }; } bench_fast_async_resampler!( f32, PolynomialDegree::Septic, bench_fast_async_septic_32, "fast async septic 32" ); bench_fast_async_resampler!( f32, PolynomialDegree::Quintic, bench_fast_async_quintic_32, "fast async quintic 32" ); bench_fast_async_resampler!( f32, PolynomialDegree::Cubic, bench_fast_async_cubic_32, "fast async cubic 32" ); bench_fast_async_resampler!( f32, PolynomialDegree::Linear, bench_fast_async_linear_32, "fast async linear 32" ); bench_fast_async_resampler!( f32, PolynomialDegree::Nearest, bench_fast_async_nearest_32, "fast async nearest 32" ); bench_fast_async_resampler!( f64, PolynomialDegree::Septic, bench_fast_async_septic_64, "fast async septic 64" ); bench_fast_async_resampler!( f64, PolynomialDegree::Quintic, bench_fast_async_quintic_64, "fast async quintic 64" ); bench_fast_async_resampler!( f64, PolynomialDegree::Cubic, bench_fast_async_cubic_64, "fast async cubic 64" ); bench_fast_async_resampler!( f64, PolynomialDegree::Linear, bench_fast_async_linear_64, "fast async linear 64" ); bench_fast_async_resampler!( f64, PolynomialDegree::Nearest, bench_fast_async_nearest_64, "fast async nearest 64" ); #[cfg(feature = "fft_resampler")] criterion_group!(fft_benches, bench_fftfixedin, bench_fftfixedin_32,); #[cfg(target_arch = "x86_64")] criterion_group!( benches, bench_fast_async_septic_32, bench_fast_async_quintic_32, bench_fast_async_cubic_32, bench_fast_async_linear_32, bench_fast_async_nearest_32, bench_fast_async_septic_64, bench_fast_async_quintic_64, bench_fast_async_cubic_64, bench_fast_async_linear_64, bench_fast_async_nearest_64, bench_scalar_async_cubic_32, bench_scalar_async_linear_32, bench_scalar_async_nearest_32, bench_scalar_async_cubic_64, bench_scalar_async_linear_64, bench_scalar_async_nearest_64, bench_sse_async_cubic_32, bench_sse_async_linear_32, bench_sse_async_nearest_32, bench_sse_async_cubic_64, bench_sse_async_linear_64, bench_sse_async_nearest_64, bench_avx_async_cubic_32, bench_avx_async_linear_32, bench_avx_async_nearest_32, bench_avx_async_cubic_64, bench_avx_async_linear_64, bench_avx_async_nearest_64, ); #[cfg(target_arch = "aarch64")] criterion_group!( benches, bench_fast_async_septic_32, bench_fast_async_quintic_32, bench_fast_async_cubic_32, bench_fast_async_linear_32, bench_fast_async_nearest_32, bench_fast_async_septic_64, bench_fast_async_quintic_64, bench_fast_async_cubic_64, bench_fast_async_linear_64, bench_fast_async_nearest_64, bench_scalar_async_cubic_32, bench_scalar_async_linear_32, bench_scalar_async_nearest_32, bench_scalar_async_cubic_64, bench_scalar_async_linear_64, bench_scalar_async_nearest_64, bench_neon_async_cubic_32, bench_neon_async_linear_32, bench_neon_async_nearest_32, bench_neon_async_cubic_64, bench_neon_async_linear_64, bench_neon_async_nearest_64, ); #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] criterion_group!( benches, bench_fast_async_septic_32, bench_fast_async_quintic_32, bench_fast_async_cubic_32, bench_fast_async_linear_32, bench_fast_async_nearest_32, bench_fast_async_septic_64, bench_fast_async_quintic_64, bench_fast_async_cubic_64, bench_fast_async_linear_64, bench_fast_async_nearest_64, bench_scalar_async_cubic_32, bench_scalar_async_linear_32, bench_scalar_async_nearest_32, bench_scalar_async_cubic_64, bench_scalar_async_linear_64, bench_scalar_async_nearest_64, ); #[cfg(feature = "fft_resampler")] criterion_main!(benches, fft_benches); #[cfg(not(feature = "fft_resampler"))] criterion_main!(benches); rubato-0.16.2/examples/analyze_result.py000064400000000000000000000030601046102023000164360ustar 00000000000000 import numpy as np import numpy.fft as fft import sys from matplotlib import pyplot as plt import math def blackman_harris(npoints): x=np.arange(0,npoints) y= 0.35875 - 0.48829*np.cos(2*np.pi*x/npoints) + 0.14128*np.cos(4*np.pi*x/npoints) - 0.01168*np.cos(6*np.pi*x/npoints) return y def plot_spect(indata, window=True): for wf, fs in indata: print(sum(wf)) npoints = len(wf) divfact = npoints/2 if window: wind = blackman_harris(npoints) wf = wf*wind*wind divfact = sum(wind)/2 print(npoints) t = np.linspace(0, npoints/fs, npoints, endpoint=False) f = np.linspace(0, fs/2.0, math.floor(npoints/2)) valfft = fft.fft(wf) cut = valfft[0:math.floor(npoints/2)] ampl = 20*np.log10(np.abs(cut)/divfact) phase = 180/np.pi*np.angle(cut) #plt.subplot(2,1,1) plt.figure(1) plt.plot(f, ampl) #plt.subplot(2,1,2) #plt.semilogx(f, phase) #plt.gca().set(xlim=(10, srate/2.0)) #plt.subplot(2,1,2) plt.figure(2) plt.plot(wf) plt.figure(3) plt.plot(np.diff(wf,5)) plt.show() file_in = sys.argv[1] channels = int(sys.argv[2]) bits = int(sys.argv[4]) srate = int(sys.argv[3]) if bits == 64: values = np.fromfile(file_in, dtype=float) elif bits == 32: values = np.fromfile(file_in, dtype=np.float32) # Let's look at the first channel only.. values = values.reshape((-1,channels)) values = values[:,0] plot_spect([(values, srate)], window=True) rubato-0.16.2/examples/fastfixedin_ramp64.rs000064400000000000000000000127661046102023000171030ustar 00000000000000extern crate rubato; use rubato::{FastFixedIn, PolynomialDegree, Resampler}; use std::convert::TryInto; use std::env; use std::fs::File; use std::io::prelude::{Read, Seek, Write}; use std::io::Cursor; use std::time::Instant; extern crate env_logger; extern crate log; use env_logger::Builder; use log::LevelFilter; ///! A resampler app that reads a raw file of little-endian 64 bit floats, and writes the output in the same format. ///! The command line arguments are input filename, output filename, input samplerate, output samplerate, ///! number of channels, final relative ratio in percent, and ramp duration in seconds. ///! To resample the file `sine_f64_2ch.raw` from 44.1kHz to 192kHz, and assuming the file has two channels, /// and that the resampling ratio should be ramped to 150% during 3 seconds, the command is: ///! ``` ///! cargo run --release --example fastfixedin_ramp64 sine_f64_2ch.raw test.raw 44100 192000 2 150 3 ///! ``` ///! There are two helper python scripts for testing. `makesineraw.py` simply writes a stereo file ///! with a 1 second long 1kHz tone (at 44.1kHz). This script takes no aruments. Modify as needed to create other test files. ///! To analyze the result, use the `analyze_result.py` script. This takes three arguments: number of channels, samplerate, and number of bits per sample (32 or 64). ///! Example, to analyze the file created above: ///! ``` ///! python examples/analyze_result.py test.raw 2 192000 64 ///! ``` /// Helper to read frames from a buffer fn read_frames(inbuffer: &mut R, nbr: usize, channels: usize) -> Vec> { let mut buffer = vec![0u8; 8]; let mut wfs = Vec::with_capacity(channels); for _chan in 0..channels { wfs.push(Vec::with_capacity(nbr)); } let mut value: f64; for _frame in 0..nbr { for wf in wfs.iter_mut().take(channels) { inbuffer.read_exact(&mut buffer).unwrap(); value = f64::from_le_bytes(buffer.as_slice().try_into().unwrap()); //idx += 8; wf.push(value); } } wfs } /// Helper to write frames to a buffer fn write_frames(waves: Vec>, outbuffer: &mut W, channels: usize) { let nbr = waves[0].len(); for frame in 0..nbr { for wave in waves.iter().take(channels) { let value64 = wave[frame]; let bytes = value64.to_le_bytes(); outbuffer.write_all(&bytes).unwrap(); } } } fn main() { // init logger let mut builder = Builder::from_default_env(); builder.filter(None, LevelFilter::Debug).init(); let file_in = env::args().nth(1).expect("Please specify an input file."); let file_out = env::args().nth(2).expect("Please specify an output file."); println!("Opening files: {}, {}", file_in, file_out); let fs_in_str = env::args() .nth(3) .expect("Please specify an input sample rate"); let fs_out_str = env::args() .nth(4) .expect("Please specify an output sample rate"); let fs_in = fs_in_str.parse::().unwrap(); let fs_out = fs_out_str.parse::().unwrap(); println!("Resampling from {} to {}", fs_in, fs_out); let channels_str = env::args() .nth(5) .expect("Please specify number of channels"); let channels = channels_str.parse::().unwrap(); let ratio_str = env::args() .nth(6) .expect("Please specify final resampling ratio in percent"); let final_ratio = ratio_str.parse::().unwrap(); let duration_str = env::args() .nth(7) .expect("Please specify ramp time in seconds"); let duration = duration_str.parse::().unwrap(); //open files let mut f_in_disk = File::open(file_in).expect("Can't open file"); let mut f_in_ram: Vec = vec![]; //let mut f_out_ram: Vec = vec![]; println!("Copy input file to buffer"); std::io::copy(&mut f_in_disk, &mut f_in_ram).unwrap(); let file_size = f_in_ram.len(); let mut f_out_ram: Vec = Vec::with_capacity(2 * (file_size as f32 * fs_out as f32 / fs_in as f32) as usize); let mut f_in = Cursor::new(&f_in_ram); let mut f_out = Cursor::new(&mut f_out_ram); // parameters let f_ratio = fs_out as f64 / fs_in as f64; let chunksize = 1024; let target_ratio = final_ratio / 100.0; let mut resampler = FastFixedIn::::new( f_ratio, target_ratio, PolynomialDegree::Cubic, chunksize, channels, ) .unwrap(); let num_chunks = f_in_ram.len() / (8 * channels * chunksize); let mut output_time = 0.0; let start = Instant::now(); for _chunk in 0..num_chunks { let waves = read_frames(&mut f_in, chunksize, channels); let waves_out = resampler.process(&waves, None).unwrap(); let new_frames = waves_out[0].len(); write_frames(waves_out, &mut f_out, channels); output_time += new_frames as f64 / fs_out as f64; if output_time < duration { let rel_time = output_time / duration; let rel_ratio = 1.0 + (target_ratio - 1.0) * rel_time; println!("time {}, rel ratio {}", output_time, rel_ratio); resampler .set_resample_ratio_relative(rel_ratio, false) .unwrap(); } } let duration = start.elapsed(); println!("Resampling took: {:?}", duration); let mut f_out_disk = File::create(file_out).unwrap(); f_out.seek(std::io::SeekFrom::Start(0)).unwrap(); std::io::copy(&mut f_out, &mut f_out_disk).unwrap(); } rubato-0.16.2/examples/fixedout_ramp64.rs000064400000000000000000000137451046102023000164240ustar 00000000000000extern crate rubato; use rubato::{ calculate_cutoff, Resampler, SincFixedOut, SincInterpolationParameters, SincInterpolationType, WindowFunction, }; use std::convert::TryInto; use std::env; use std::fs::File; use std::io::prelude::{Read, Seek, Write}; use std::io::Cursor; use std::time::Instant; extern crate env_logger; extern crate log; use env_logger::Builder; use log::LevelFilter; ///! A resampler app that reads a raw file of little-endian 64 bit floats, and writes the output in the same format. ///! While resampling, it ramps the resampling ratio from 100% to a user-provided value, during a given time duration (measured in output time). ///! This version takes a varying number of input samples per chunk, and outputs a fixed number of samples. ///! The command line arguments are input filename, output filename, input samplerate, output samplerate, ///! number of channels, final relative ratio in percent, and ramp duration in seconds. ///! To resample the file `sine_f64_2ch.raw` from 44.1kHz to 192kHz, and assuming the file has two channels, /// and that the resampling ratio should be ramped to 150% during 3 seconds, the command is: ///! ``` ///! cargo run --release --example fixedout_ramp64 sine_f64_2ch.raw test.raw 44100 192000 2 150 3 ///! ``` ///! There are two helper python scripts for testing. `makesineraw.py` simply writes a stereo file ///! with a 1 second long 1kHz tone (at 44.1kHz). This script takes no aruments. Modify as needed to create other test files. ///! To analyze the result, use the `analyze_result.py` script. This takes three arguments: number of channels, samplerate, and number of bits per sample (32 or 64). ///! Example, to analyze the file created above: ///! ``` ///! python examples/analyze_result.py test.raw 2 192000 64 ///! ``` fn read_frames(inbuffer: &mut R, nbr: usize, channels: usize) -> Vec> { let mut buffer = vec![0u8; 8]; let mut wfs = Vec::with_capacity(channels); for _chan in 0..channels { wfs.push(Vec::with_capacity(nbr)); } let mut value: f64; for _frame in 0..nbr { for wf in wfs.iter_mut().take(channels) { if inbuffer.read(&mut buffer).unwrap() < 8 { return wfs; } value = f64::from_le_bytes(buffer.as_slice().try_into().unwrap()); //idx += 8; wf.push(value); } } wfs } fn write_frames(waves: Vec>, outbuffer: &mut W, channels: usize) { let nbr = waves[0].len(); for frame in 0..nbr { for wave in waves.iter().take(channels) { let value64 = wave[frame]; let bytes = value64.to_le_bytes(); outbuffer.write_all(&bytes).unwrap(); } } } fn main() { // init logger let mut builder = Builder::from_default_env(); builder.filter(None, LevelFilter::Trace).init(); let file_in = env::args().nth(1).expect("Please specify an input file."); let file_out = env::args().nth(2).expect("Please specify an output file."); println!("Opening files: {}, {}", file_in, file_out); let fs_in_str = env::args() .nth(3) .expect("Please specify an input sample rate"); let fs_out_str = env::args() .nth(4) .expect("Please specify an output sample rate"); let fs_in = fs_in_str.parse::().unwrap(); let fs_out = fs_out_str.parse::().unwrap(); println!("Resampling from {} to {}", fs_in, fs_out); let channels_str = env::args() .nth(5) .expect("Please specify number of channels"); let channels = channels_str.parse::().unwrap(); let ratio_str = env::args() .nth(6) .expect("Please specify final resampling ratio in percent"); let final_ratio = ratio_str.parse::().unwrap(); let duration_str = env::args() .nth(7) .expect("Please specify ramp time in seconds"); let duration = duration_str.parse::().unwrap(); //open files let mut f_in_disk = File::open(file_in).expect("Can't open file"); let mut f_in_ram: Vec = vec![]; let mut f_out_ram: Vec = vec![]; println!("Copy input file to buffer"); std::io::copy(&mut f_in_disk, &mut f_in_ram).unwrap(); let mut f_in = Cursor::new(&f_in_ram); let mut f_out = Cursor::new(&mut f_out_ram); let f_ratio = fs_out as f64 / fs_in as f64; // Balanced for async, see the fixedin64 example for more config examples let sinc_len = 128; let oversampling_factor = 2048; let interpolation = SincInterpolationType::Linear; let window = WindowFunction::Blackman2; let f_cutoff = calculate_cutoff(sinc_len, window); let params = SincInterpolationParameters { sinc_len, f_cutoff, interpolation, oversampling_factor, window, }; let chunksize = 1024; let target_ratio = final_ratio / 100.0; let mut resampler = SincFixedOut::::new(f_ratio, target_ratio, params, chunksize, channels).unwrap(); let start = Instant::now(); let mut output_time = 0.0; loop { let nbr_frames = resampler.input_frames_next(); let waves = read_frames(&mut f_in, nbr_frames, channels); if waves[0].len() < nbr_frames { break; } let waves_out = resampler.process(&waves, None).unwrap(); write_frames(waves_out, &mut f_out, channels); output_time += chunksize as f64 / fs_out as f64; if output_time < duration { let rel_time = output_time / duration; let rel_ratio = 1.0 + (target_ratio - 1.0) * rel_time; println!("time {}, rel ratio {}", output_time, rel_ratio); resampler .set_resample_ratio_relative(rel_ratio, true) .unwrap(); } } let duration = start.elapsed(); println!("Resampling took: {:?}", duration); let mut f_out_disk = File::create(file_out).unwrap(); f_out.seek(std::io::SeekFrom::Start(0)).unwrap(); std::io::copy(&mut f_out, &mut f_out_disk).unwrap(); } rubato-0.16.2/examples/makemultitone.py000064400000000000000000000012131046102023000162510ustar 00000000000000# Make a signal for testing purposes import numpy as np t = np.linspace(0, 1.0, num=int(1.0*44100), endpoint=False) #freqs = [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000] #ampls = [500000/(f*f) for f in freqs] freqs = list(range(1000, 21000, 1000)) ampls = [1.0/len(freqs)]*len(freqs) wave = np.zeros(len(t)) for f, a in zip(freqs, ampls): wave = wave + a * np.sin(f*2*np.pi*t) wave= np.reshape(wave,(-1,1)) wave = np.concatenate((wave, wave), axis=1) wave64 = wave.astype('float64') wave32 = wave.astype('float32') #print(wave64) wave64.tofile("multi_44.1_f64_2ch_1.0s.raw") wave32.tofile("multi_44.1_f32_2ch_1.0s.raw") rubato-0.16.2/examples/makesineraw.py000064400000000000000000000006631046102023000157110ustar 00000000000000# Make a sine for testing purposes # Store as interleaved stereo import numpy as np t = np.linspace(0, 10.0, num=int(10.0*44100), endpoint=False) wave = np.sin(1000*2*np.pi*t) wave= np.reshape(wave,(-1,1)) wave = np.concatenate((wave, wave), axis=1) wave64 = wave.astype('float64') wave32 = wave.astype('float32') #print(wave64) wave64.tofile("sine_44.1_1000_f64_2ch_10.0s.raw") wave32.tofile("sine_44.1_1000_f32_2ch_10.0s.raw") rubato-0.16.2/examples/makespike.py000064400000000000000000000002451046102023000153500ustar 00000000000000# Make a simple spike for testing purposes import numpy as np spike = np.zeros(2048, dtype="float64") spike[0] = 1.0 spike[512]=1.0 spike.tofile("spike_1024.raw")rubato-0.16.2/examples/process_f64.rs000064400000000000000000000210621046102023000155300ustar 00000000000000extern crate rubato; use rubato::{ calculate_cutoff, implement_resampler, FastFixedIn, FastFixedOut, PolynomialDegree, SincFixedIn, SincFixedOut, SincInterpolationParameters, SincInterpolationType, WindowFunction, }; #[cfg(feature = "fft_resampler")] use rubato::{FftFixedIn, FftFixedInOut, FftFixedOut}; use std::convert::TryInto; use std::env; use std::fs::File; use std::io::prelude::{Read, Seek, Write}; use std::io::BufReader; use std::time::Instant; extern crate env_logger; extern crate log; use env_logger::Builder; use log::LevelFilter; const BYTE_PER_SAMPLE: usize = 8; // A resampler app that reads a raw file of little-endian 64 bit floats, and writes the output in the same format. // The command line arguments are resampler type, input filename, output filename, input samplerate, output samplerate, number of channels // To use a SincFixedIn resampler to resample the file `sine_f64_2ch.raw` from 44.1kHz to 192kHz, and assuming the file has two channels, the command is: // ``` // cargo run --release --example process_f64 SincFixedIn sine_f64_2ch.raw test.raw 44100 192000 2 // ``` // There are two helper python scripts for testing. `makesineraw.py` simply writes a stereo file // with a 1 second long 1kHz tone (at 44.1kHz). This script takes no aruments. Modify as needed to create other test files. // To analyze the result, use the `analyze_result.py` script. This takes three arguments: number of channels, samplerate, and number of bits per sample (32 or 64). // Example, to analyze the file created above: // ``` // python examples/analyze_result.py test.raw 2 192000 64 // ``` // Implement an object safe resampler with the input and output types needed in this example. implement_resampler!(SliceResampler, &[&[T]], &mut [Vec]); /// Helper to read an entire file to memory fn read_file(inbuffer: &mut R, channels: usize) -> Vec> { let mut buffer = vec![0u8; BYTE_PER_SAMPLE]; let mut wfs = Vec::with_capacity(channels); for _chan in 0..channels { wfs.push(Vec::new()); } 'outer: loop { for wf in wfs.iter_mut() { let bytes_read = inbuffer.read(&mut buffer).unwrap(); if bytes_read == 0 { break 'outer; } let value = f64::from_le_bytes(buffer.as_slice().try_into().unwrap()); //idx += 8; wf.push(value); } } wfs } /// Helper to write all frames to a file fn write_frames( waves: Vec>, output: &mut W, frames_to_skip: usize, frames_to_write: usize, ) { let channels = waves.len(); let end = (frames_to_skip + frames_to_write).min(waves[0].len() - 1); for frame in frames_to_skip..end { for wave in waves.iter().take(channels) { let value64 = wave[frame]; let bytes = value64.to_le_bytes(); output.write_all(&bytes).unwrap(); } } } fn append_frames(buffers: &mut [Vec], additional: &[Vec], nbr_frames: usize) { buffers .iter_mut() .zip(additional.iter()) .for_each(|(b, a)| b.extend_from_slice(&a[..nbr_frames])); } fn main() { // init logger let mut builder = Builder::from_default_env(); builder.filter(None, LevelFilter::Debug).init(); let resampler_type = env::args() .nth(1) .expect("Please specify a resampler type, one of:\nSincFixedIn\nSincFixedOut\nFastFixedIn\nFastFixedOut\nFftFixedIn\nFftFixedOut\nFftFixedInOut"); let file_in = env::args().nth(2).expect("Please specify an input file."); let file_out = env::args().nth(3).expect("Please specify an output file."); println!("Opening files: {}, {}", file_in, file_out); let fs_in_str = env::args() .nth(4) .expect("Please specify an input sample rate"); let fs_out_str = env::args() .nth(5) .expect("Please specify an output sample rate"); let fs_in = fs_in_str.parse::().unwrap(); let fs_out = fs_out_str.parse::().unwrap(); println!("Resampling from {} to {}", fs_in, fs_out); let channels_str = env::args() .nth(6) .expect("Please specify number of channels"); let channels = channels_str.parse::().unwrap(); println!("Copy input file to buffer"); let file_in_disk = File::open(file_in).expect("Can't open file"); let mut file_in_reader = BufReader::new(file_in_disk); let indata = read_file(&mut file_in_reader, channels); let nbr_input_frames = indata[0].len(); // Create buffer for storing output let mut outdata = vec![ Vec::with_capacity( 2 * (nbr_input_frames as f32 * fs_out as f32 / fs_in as f32) as usize ); channels ]; let f_ratio = fs_out as f64 / fs_in as f64; // Create resampler let mut resampler: Box> = match resampler_type.as_str() { "SincFixedIn" => { let sinc_len = 128; let oversampling_factor = 256; let interpolation = SincInterpolationType::Quadratic; let window = WindowFunction::Blackman2; let f_cutoff = calculate_cutoff(sinc_len, window); let params = SincInterpolationParameters { sinc_len, f_cutoff, interpolation, oversampling_factor, window, }; Box::new(SincFixedIn::::new(f_ratio, 1.1, params, 1024, channels).unwrap()) } "SincFixedOut" => { let sinc_len = 128; let oversampling_factor = 512; let interpolation = SincInterpolationType::Cubic; let window = WindowFunction::Blackman2; let f_cutoff = calculate_cutoff(sinc_len, window); let params = SincInterpolationParameters { sinc_len, f_cutoff, interpolation, oversampling_factor, window, }; Box::new(SincFixedOut::::new(f_ratio, 1.1, params, 1024, channels).unwrap()) } "FastFixedIn" => { Box::new(FastFixedIn::::new(f_ratio, 1.1, PolynomialDegree::Septic, 1024, channels).unwrap()) } "FastFixedOut" => { Box::new(FastFixedOut::::new(f_ratio, 1.1, PolynomialDegree::Septic, 1024, channels).unwrap()) } #[cfg(feature = "fft_resampler")] "FftFixedIn" => { Box::new(FftFixedIn::::new(fs_in, fs_out, 1024, 2, channels).unwrap()) } #[cfg(feature = "fft_resampler")] "FftFixedOut" => { Box::new(FftFixedOut::::new(fs_in, fs_out, 1024, 2, channels).unwrap()) } #[cfg(feature = "fft_resampler")] "FftFixedInOut" => { Box::new(FftFixedInOut::::new(fs_in, fs_out, 1024, channels).unwrap()) } _ => panic!("Unknown resampler type {}\nMust be one of SincFixedIn, SincFixedOut, FastFixedIn, FastFixedOut, FftFixedIn, FftFixedOut, FftFixedInOut", resampler_type), }; // Prepare let mut input_frames_next = resampler.input_frames_next(); let resampler_delay = resampler.output_delay(); let mut outbuffer = vec![vec![0.0f64; resampler.output_frames_max()]; channels]; let mut indata_slices: Vec<&[f64]> = indata.iter().map(|v| &v[..]).collect(); // Process all full chunks let start = Instant::now(); while indata_slices[0].len() >= input_frames_next { let (nbr_in, nbr_out) = resampler .process_into_buffer(&indata_slices, &mut outbuffer, None) .unwrap(); for chan in indata_slices.iter_mut() { *chan = &chan[nbr_in..]; } append_frames(&mut outdata, &outbuffer, nbr_out); input_frames_next = resampler.input_frames_next(); } // Process a partial chunk with the last frames. if !indata_slices[0].is_empty() { let (_nbr_in, nbr_out) = resampler .process_partial_into_buffer(Some(&indata_slices), &mut outbuffer, None) .unwrap(); append_frames(&mut outdata, &outbuffer, nbr_out); } let duration = start.elapsed(); println!("Resampling took: {:?}", duration); let nbr_output_frames = (nbr_input_frames as f32 * fs_out as f32 / fs_in as f32) as usize; println!( "Processed {} input frames into {} output frames", nbr_input_frames, nbr_output_frames ); // Write output to file, trimming off the silent frames from both ends. let mut file_out_disk = File::create(file_out).unwrap(); write_frames( outdata, &mut file_out_disk, resampler_delay, nbr_output_frames, ); } rubato-0.16.2/src/asynchro_fast.rs000064400000000000000000001323271046102023000152160ustar 00000000000000use crate::error::{ResampleError, ResampleResult, ResamplerConstructionError}; use crate::{update_mask_from_buffers, validate_buffers, Resampler, Sample}; const POLYNOMIAL_LEN_U: usize = 8; const POLYNOMIAL_LEN_I: isize = 8; macro_rules! t { // Shorter form of T::coerce(value) ($expression:expr) => { T::coerce($expression) }; } /// Degree of the polynomial used for interpolation. /// A higher degree gives a higher quality result, while taking longer to compute. #[derive(Debug)] pub enum PolynomialDegree { /// Septic polynomial, fitted using 8 sample points. Septic, /// Quintic polynomial, fitted using 6 sample points. Quintic, /// Cubic polynomial, fitted using 4 sample points. Cubic, /// Linear polynomial, fitted using 2 sample points. Linear, /// Nearest, uses the nearest sample point without any fitting. Nearest, } /// An asynchronous resampler that accepts a fixed number of audio frames for input /// and returns a variable number of frames. /// /// The resampling is done by interpolating between the input samples by fitting polynomials. /// The polynomial degree can selected, see [PolynomialDegree] for the available options. /// /// Note that no anti-aliasing filter is used. /// This makes it run considerably faster than the corresponding SincFixedIn, which performs anti-aliasing filtering. /// The price is that the resampling creates some artefacts in the output, mainly at higher frequencies. /// Use SincFixedIn if this can not be tolerated. /// /// The resampling ratio can be freely adjusted within the range specified to the constructor. /// Higher maximum ratios require more memory to be allocated by [Resampler::output_buffer_allocate]. pub struct FastFixedIn { nbr_channels: usize, chunk_size: usize, last_index: f64, resample_ratio: f64, resample_ratio_original: f64, target_ratio: f64, max_relative_ratio: f64, buffer: Vec>, interpolation: PolynomialDegree, channel_mask: Vec, } /// An asynchronous resampler that returns a fixed number of audio frames. /// The number of input frames required is given by the /// [input_frames_next](Resampler::input_frames_next) function. /// /// The resampling is done by interpolating between the input samples. /// The polynomial degree can be selected, see [PolynomialDegree] for the available options. /// /// Note that no anti-aliasing filter is used. /// This makes it run considerably faster than the corresponding SincFixedOut, which performs anti-aliasing filtering. /// The price is that the resampling creates some artefacts in the output, mainly at higher frequencies. /// Use SincFixedOut if this can not be tolerated. /// /// The resampling ratio can be freely adjusted within the range specified to the constructor. /// Higher maximum ratios require more memory to be allocated by /// [input_buffer_allocate](Resampler::input_buffer_allocate) and an internal buffer. pub struct FastFixedOut { nbr_channels: usize, chunk_size: usize, needed_input_size: usize, last_index: f64, current_buffer_fill: usize, resample_ratio: f64, resample_ratio_original: f64, target_ratio: f64, max_relative_ratio: f64, buffer: Vec>, interpolation: PolynomialDegree, channel_mask: Vec, } /// Perform septic polynomial interpolation to get value at x. /// Input points are assumed to be at x = -3, -2, -1, 0, 1, 2, 3, 4. fn interp_septic(x: T, yvals: &[T]) -> T where T: Sample, { let a = yvals[0]; let b = yvals[1]; let c = yvals[2]; let d = yvals[3]; let e = yvals[4]; let f = yvals[5]; let g = yvals[6]; let h = yvals[7]; let k7 = -a + t!(7.0) * b - t!(21.0) * c + t!(35.0) * d - t!(35.0) * e + t!(21.0) * f - t!(7.0) * g + h; let k6 = t!(7.0) * a - t!(42.0) * b + t!(105.0) * c - t!(140.0) * d + t!(105.0) * e - t!(42.0) * f + t!(7.0) * g; let k5 = -t!(7.0) * a - t!(14.0) * b + t!(189.0) * c - t!(490.0) * d + t!(595.0) * e - t!(378.0) * f + t!(119.0) * g - t!(14.0) * h; let k4 = -t!(35.0) * a + t!(420.0) * b - t!(1365.0) * c + t!(1960.0) * d - t!(1365.0) * e + t!(420.0) * f - t!(35.0) * g; let k3 = t!(56.0) * a - t!(497.0) * b + t!(336.0) * c + t!(1715.0) * d - t!(3080.0) * e + t!(1869.0) * f - t!(448.0) * g + t!(49.0) * h; let k2 = t!(28.0) * a - t!(378.0) * b + t!(3780.0) * c - t!(6860.0) * d + t!(3780.0) * e - t!(378.0) * f + t!(28.0) * g; let k1 = -t!(48.0) * a + t!(504.0) * b - t!(3024.0) * c - t!(1260.0) * d + t!(5040.0) * e - t!(1512.0) * f + t!(336.0) * g - t!(36.0) * h; let k0 = t!(5040.0) * d; let x2 = x * x; let x3 = x2 * x; let x4 = x2 * x2; let x5 = x2 * x3; let x6 = x3 * x3; let x7 = x3 * x4; let val = k7 * x7 + k6 * x6 + k5 * x5 + k4 * x4 + k3 * x3 + k2 * x2 + k1 * x + k0; t!(1.0 / 5040.0) * val } /// Perform quintic polynomial interpolation to get value at x. /// Input points are assumed to be at x = -2, -1, 0, 1, 2, 3. fn interp_quintic(x: T, yvals: &[T]) -> T where T: Sample, { let a = yvals[0]; let b = yvals[1]; let c = yvals[2]; let d = yvals[3]; let e = yvals[4]; let f = yvals[5]; let k5 = -a + t!(5.0) * b - t!(10.0) * c + t!(10.0) * d - t!(5.0) * e + f; let k4 = t!(5.0) * a - t!(20.0) * b + t!(30.0) * c - t!(20.0) * d + t!(5.0) * e; let k3 = -t!(5.0) * a - t!(5.0) * b + t!(50.0) * c - t!(70.0) * d + t!(35.0) * e - t!(5.0) * f; let k2 = -t!(5.0) * a + t!(80.0) * b - t!(150.0) * c + t!(80.0) * d - t!(5.0) * e; let k1 = t!(6.0) * a - t!(60.0) * b - t!(40.0) * c + t!(120.0) * d - t!(30.0) * e + t!(4.0) * f; let k0 = t!(120.0) * c; let x2 = x * x; let x3 = x2 * x; let x4 = x2 * x2; let x5 = x2 * x3; let val = k5 * x5 + k4 * x4 + k3 * x3 + k2 * x2 + k1 * x + k0; t!(1.0 / 120.0) * val } /// Perform cubic polynomial interpolation to get value at x. /// Input points are assumed to be at x = -1, 0, 1, 2. fn interp_cubic(x: T, yvals: &[T]) -> T where T: Sample, { let a0 = yvals[1]; let a1 = -t!(1.0 / 3.0) * yvals[0] - t!(0.5) * yvals[1] + yvals[2] - t!(1.0 / 6.0) * yvals[3]; let a2 = t!(0.5) * (yvals[0] + yvals[2]) - yvals[1]; let a3 = t!(0.5) * (yvals[1] - yvals[2]) + t!(1.0 / 6.0) * (yvals[3] - yvals[0]); let x2 = x * x; let x3 = x2 * x; a0 + a1 * x + a2 * x2 + a3 * x3 } /// Linear interpolation between two points at x=0 and x=1. fn interp_lin(x: T, yvals: &[T]) -> T where T: Sample, { yvals[0] + x * (yvals[1] - yvals[0]) } fn validate_ratios( resample_ratio: f64, max_resample_ratio_relative: f64, ) -> Result<(), ResamplerConstructionError> { if resample_ratio <= 0.0 { return Err(ResamplerConstructionError::InvalidRatio(resample_ratio)); } if max_resample_ratio_relative < 1.0 { return Err(ResamplerConstructionError::InvalidRelativeRatio( max_resample_ratio_relative, )); } Ok(()) } impl FastFixedIn where T: Sample, { /// Create a new FastFixedIn. /// /// Parameters are: /// - `resample_ratio`: Starting ratio between output and input sample rates, must be > 0. /// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Resampler::set_resample_ratio] relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio * 10.0` and `resample_ratio / 10.0`. /// - `interpolation_type`: Degree of polynomial used for interpolation, see [PolynomialDegree]. /// - `chunk_size`: Size of input data in frames. /// - `nbr_channels`: Number of channels in input/output. pub fn new( resample_ratio: f64, max_resample_ratio_relative: f64, interpolation_type: PolynomialDegree, chunk_size: usize, nbr_channels: usize, ) -> Result { debug!( "Create new FastFixedIn, ratio: {}, chunk_size: {}, channels: {}", resample_ratio, chunk_size, nbr_channels, ); validate_ratios(resample_ratio, max_resample_ratio_relative)?; let buffer = vec![vec![T::zero(); chunk_size + 2 * POLYNOMIAL_LEN_U]; nbr_channels]; let channel_mask = vec![true; nbr_channels]; Ok(FastFixedIn { nbr_channels, chunk_size, last_index: -(POLYNOMIAL_LEN_I / 2) as f64, resample_ratio, resample_ratio_original: resample_ratio, target_ratio: resample_ratio, max_relative_ratio: max_resample_ratio_relative, buffer, interpolation: interpolation_type, channel_mask, }) } } impl Resampler for FastFixedIn where T: Sample, { fn process_into_buffer, Vout: AsMut<[T]>>( &mut self, wave_in: &[Vin], wave_out: &mut [Vout], active_channels_mask: Option<&[bool]>, ) -> ResampleResult<(usize, usize)> { if let Some(mask) = active_channels_mask { self.channel_mask.copy_from_slice(mask); } else { update_mask_from_buffers(&mut self.channel_mask); }; // Set length to chunksize*ratio plus a safety margin of 10 elements. let needed_len = (self.chunk_size as f64 * (0.5 * self.resample_ratio + 0.5 * self.target_ratio) + 10.0) as usize; validate_buffers( wave_in, wave_out, &self.channel_mask, self.nbr_channels, self.chunk_size, needed_len, )?; // Update buffer with new data. for buf in self.buffer.iter_mut() { buf.copy_within(self.chunk_size..self.chunk_size + 2 * POLYNOMIAL_LEN_U, 0); } for (chan, active) in self.channel_mask.iter().enumerate() { if *active { self.buffer[chan][2 * POLYNOMIAL_LEN_U..2 * POLYNOMIAL_LEN_U + self.chunk_size] .copy_from_slice(&wave_in[chan].as_ref()[..self.chunk_size]); } } let mut t_ratio = 1.0 / self.resample_ratio; let t_ratio_end = 1.0 / self.target_ratio; let approximate_nbr_frames = self.chunk_size as f64 * (0.5 * self.resample_ratio + 0.5 * self.target_ratio); let t_ratio_increment = (t_ratio_end - t_ratio) / approximate_nbr_frames; let end_idx = self.chunk_size as isize - (POLYNOMIAL_LEN_I + 1) - t_ratio_end.ceil() as isize; //println!( // "start ratio {}, end_ratio {}, frames {}, t_increment {}", // t_ratio, // t_ratio_end, // approximate_nbr_frames, // t_ratio_increment //); let mut idx = self.last_index; let mut n = 0; match self.interpolation { PolynomialDegree::Septic => { while idx < end_idx as f64 { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); let start_idx = idx_floor as isize - 3; let frac = idx - idx_floor; let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { unsafe { let buf = self.buffer.get_unchecked(chan).get_unchecked( (start_idx + 2 * POLYNOMIAL_LEN_I) as usize ..(start_idx + 2 * POLYNOMIAL_LEN_I + 8) as usize, ); *wave_out .get_unchecked_mut(chan) .as_mut() .get_unchecked_mut(n) = interp_septic(frac_offset, buf); } } } n += 1; } } PolynomialDegree::Quintic => { while idx < end_idx as f64 { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); let start_idx = idx_floor as isize - 2; let frac = idx - idx_floor; let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { unsafe { let buf = self.buffer.get_unchecked(chan).get_unchecked( (start_idx + 2 * POLYNOMIAL_LEN_I) as usize ..(start_idx + 2 * POLYNOMIAL_LEN_I + 6) as usize, ); *wave_out .get_unchecked_mut(chan) .as_mut() .get_unchecked_mut(n) = interp_quintic(frac_offset, buf); } } } n += 1; } } PolynomialDegree::Cubic => { while idx < end_idx as f64 { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); let start_idx = idx_floor as isize - 1; let frac = idx - idx_floor; let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { unsafe { let buf = self.buffer.get_unchecked(chan).get_unchecked( (start_idx + 2 * POLYNOMIAL_LEN_I) as usize ..(start_idx + 2 * POLYNOMIAL_LEN_I + 4) as usize, ); *wave_out .get_unchecked_mut(chan) .as_mut() .get_unchecked_mut(n) = interp_cubic(frac_offset, buf); } } } n += 1; } } PolynomialDegree::Linear => { while idx < end_idx as f64 { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); let start_idx = idx_floor as isize; let frac = idx - idx_floor; let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { unsafe { let buf = self.buffer.get_unchecked(chan).get_unchecked( (start_idx + 2 * POLYNOMIAL_LEN_I) as usize ..(start_idx + 2 * POLYNOMIAL_LEN_I + 2) as usize, ); *wave_out .get_unchecked_mut(chan) .as_mut() .get_unchecked_mut(n) = interp_lin(frac_offset, buf); } } } n += 1; } } PolynomialDegree::Nearest => { while idx < end_idx as f64 { t_ratio += t_ratio_increment; idx += t_ratio; let start_idx = idx.floor() as isize; for (chan, active) in self.channel_mask.iter().enumerate() { if *active { unsafe { let point = self .buffer .get_unchecked(chan) .get_unchecked((start_idx + 2 * POLYNOMIAL_LEN_I) as usize); *wave_out .get_unchecked_mut(chan) .as_mut() .get_unchecked_mut(n) = *point; } } } n += 1; } } } // Store last index for next iteration. self.last_index = idx - self.chunk_size as f64; self.resample_ratio = self.target_ratio; trace!( "Resampling channels {:?}, {} frames in, {} frames out", active_channels_mask, self.chunk_size, n, ); Ok((self.chunk_size, n)) } fn output_frames_max(&self) -> usize { // Set length to chunksize*ratio plus a safety margin of 10 elements. (self.chunk_size as f64 * self.resample_ratio_original * self.max_relative_ratio + 10.0) as usize } fn output_frames_next(&self) -> usize { (self.chunk_size as f64 * (0.5 * self.resample_ratio + 0.5 * self.target_ratio) + 10.0) as usize } fn output_delay(&self) -> usize { (POLYNOMIAL_LEN_U as f64 * self.resample_ratio / 2.0) as usize } fn nbr_channels(&self) -> usize { self.nbr_channels } fn input_frames_max(&self) -> usize { self.chunk_size } fn input_frames_next(&self) -> usize { self.chunk_size } fn set_resample_ratio(&mut self, new_ratio: f64, ramp: bool) -> ResampleResult<()> { trace!("Change resample ratio to {}", new_ratio); if (new_ratio / self.resample_ratio_original >= 1.0 / self.max_relative_ratio) && (new_ratio / self.resample_ratio_original <= self.max_relative_ratio) { if !ramp { self.resample_ratio = new_ratio; } self.target_ratio = new_ratio; Ok(()) } else { Err(ResampleError::RatioOutOfBounds { provided: new_ratio, original: self.resample_ratio_original, max_relative_ratio: self.max_relative_ratio, }) } } fn set_resample_ratio_relative(&mut self, rel_ratio: f64, ramp: bool) -> ResampleResult<()> { let new_ratio = self.resample_ratio_original * rel_ratio; self.set_resample_ratio(new_ratio, ramp) } fn reset(&mut self) { self.buffer .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero())); self.channel_mask.iter_mut().for_each(|val| *val = true); self.last_index = -(POLYNOMIAL_LEN_I / 2) as f64; self.resample_ratio = self.resample_ratio_original; self.target_ratio = self.resample_ratio_original; } } impl FastFixedOut where T: Sample, { /// Create a new FastFixedOut. /// /// Parameters are: /// - `resample_ratio`: Starting ratio between output and input sample rates, must be > 0. /// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Resampler::set_resample_ratio] relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio * 10.0` and `resample_ratio / 10.0`. /// - `interpolation_type`: Degree of polynomial used for interpolation, see [PolynomialDegree]. /// - `chunk_size`: Size of output data in frames. /// - `nbr_channels`: Number of channels in input/output. pub fn new( resample_ratio: f64, max_resample_ratio_relative: f64, interpolation_type: PolynomialDegree, chunk_size: usize, nbr_channels: usize, ) -> Result { debug!( "Create new FastFixedOut, ratio: {}, chunk_size: {}, channels: {}", resample_ratio, chunk_size, nbr_channels, ); validate_ratios(resample_ratio, max_resample_ratio_relative)?; let needed_input_size = (chunk_size as f64 / resample_ratio).ceil() as usize + POLYNOMIAL_LEN_U / 2; let buffer_channel_length = ((max_resample_ratio_relative + 1.0) * needed_input_size as f64) as usize + 2 * POLYNOMIAL_LEN_U; let buffer = vec![vec![T::zero(); buffer_channel_length]; nbr_channels]; let channel_mask = vec![true; nbr_channels]; Ok(FastFixedOut { nbr_channels, chunk_size, needed_input_size, last_index: -(POLYNOMIAL_LEN_I / 2) as f64, current_buffer_fill: needed_input_size, resample_ratio, resample_ratio_original: resample_ratio, target_ratio: resample_ratio, max_relative_ratio: max_resample_ratio_relative, buffer, interpolation: interpolation_type, channel_mask, }) } } impl Resampler for FastFixedOut where T: Sample, { fn process_into_buffer, Vout: AsMut<[T]>>( &mut self, wave_in: &[Vin], wave_out: &mut [Vout], active_channels_mask: Option<&[bool]>, ) -> ResampleResult<(usize, usize)> { if let Some(mask) = active_channels_mask { self.channel_mask.copy_from_slice(mask); } else { update_mask_from_buffers(&mut self.channel_mask); }; validate_buffers( wave_in, wave_out, &self.channel_mask, self.nbr_channels, self.needed_input_size, self.chunk_size, )?; for buf in self.buffer.iter_mut() { buf.copy_within( self.current_buffer_fill..self.current_buffer_fill + 2 * POLYNOMIAL_LEN_U, 0, ); } self.current_buffer_fill = self.needed_input_size; for (chan, wave_in) in wave_in .iter() .enumerate() .filter(|(chan, _)| self.channel_mask[*chan]) { debug_assert!(self.chunk_size <= wave_out[chan].as_mut().len()); self.buffer[chan][2 * POLYNOMIAL_LEN_U..2 * POLYNOMIAL_LEN_U + self.needed_input_size] .copy_from_slice(&wave_in.as_ref()[..self.needed_input_size]); } let mut idx = self.last_index; let mut t_ratio = 1.0 / self.resample_ratio; let t_ratio_end = 1.0 / self.target_ratio; let t_ratio_increment = (t_ratio_end - t_ratio) / self.chunk_size as f64; match self.interpolation { PolynomialDegree::Septic => { for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); let start_idx = idx_floor as isize - 3; let frac = idx - idx_floor; let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { unsafe { let buf = self.buffer.get_unchecked(chan).get_unchecked( (start_idx + 2 * POLYNOMIAL_LEN_I) as usize ..(start_idx + 2 * POLYNOMIAL_LEN_I + 8) as usize, ); *wave_out .get_unchecked_mut(chan) .as_mut() .get_unchecked_mut(frame) = interp_septic(frac_offset, buf); } } } } } PolynomialDegree::Quintic => { for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); let start_idx = idx_floor as isize - 2; let frac = idx - idx_floor; let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { unsafe { let buf = self.buffer.get_unchecked(chan).get_unchecked( (start_idx + 2 * POLYNOMIAL_LEN_I) as usize ..(start_idx + 2 * POLYNOMIAL_LEN_I + 6) as usize, ); *wave_out .get_unchecked_mut(chan) .as_mut() .get_unchecked_mut(frame) = interp_quintic(frac_offset, buf); } } } } } PolynomialDegree::Cubic => { for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); let start_idx = idx_floor as isize - 1; let frac = idx - idx_floor; let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { unsafe { let buf = self.buffer.get_unchecked(chan).get_unchecked( (start_idx + 2 * POLYNOMIAL_LEN_I) as usize ..(start_idx + 2 * POLYNOMIAL_LEN_I + 4) as usize, ); *wave_out .get_unchecked_mut(chan) .as_mut() .get_unchecked_mut(frame) = interp_cubic(frac_offset, buf); } } } } } PolynomialDegree::Linear => { for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); let start_idx = idx_floor as isize; let frac = idx - idx_floor; let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { unsafe { let buf = self.buffer.get_unchecked(chan).get_unchecked( (start_idx + 2 * POLYNOMIAL_LEN_I) as usize ..(start_idx + 2 * POLYNOMIAL_LEN_I + 2) as usize, ); *wave_out .get_unchecked_mut(chan) .as_mut() .get_unchecked_mut(frame) = interp_lin(frac_offset, buf); } } } } } PolynomialDegree::Nearest => { for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; let start_idx = idx.floor() as isize; for (chan, active) in self.channel_mask.iter().enumerate() { if *active { unsafe { let point = self .buffer .get_unchecked(chan) .get_unchecked((start_idx + 2 * POLYNOMIAL_LEN_I) as usize); *wave_out .get_unchecked_mut(chan) .as_mut() .get_unchecked_mut(frame) = *point; } } } } } } // Store last index for next iteration. let input_frames_used = self.needed_input_size; self.last_index = idx - self.current_buffer_fill as f64; self.resample_ratio = self.target_ratio; self.needed_input_size = (self.last_index as f32 + self.chunk_size as f32 / self.resample_ratio as f32 + POLYNOMIAL_LEN_U as f32) .ceil() as usize; trace!( "Resampling channels {:?}, {} frames in, {} frames out. Next needed length: {} frames, last index {}", active_channels_mask, self.current_buffer_fill, self.chunk_size, self.needed_input_size, self.last_index ); Ok((input_frames_used, self.chunk_size)) } fn input_frames_max(&self) -> usize { (self.chunk_size as f64 / self.resample_ratio_original * self.max_relative_ratio).ceil() as usize + 2 + POLYNOMIAL_LEN_U / 2 } fn input_frames_next(&self) -> usize { self.needed_input_size } fn nbr_channels(&self) -> usize { self.nbr_channels } fn output_frames_max(&self) -> usize { self.chunk_size } fn output_frames_next(&self) -> usize { self.chunk_size } fn output_delay(&self) -> usize { (POLYNOMIAL_LEN_U as f64 * self.resample_ratio / 2.0) as usize } fn set_resample_ratio(&mut self, new_ratio: f64, ramp: bool) -> ResampleResult<()> { trace!("Change resample ratio to {}", new_ratio); if (new_ratio / self.resample_ratio_original >= 1.0 / self.max_relative_ratio) && (new_ratio / self.resample_ratio_original <= self.max_relative_ratio) { if !ramp { self.resample_ratio = new_ratio; } self.target_ratio = new_ratio; self.needed_input_size = (self.last_index as f32 + self.chunk_size as f32 / (0.5 * self.resample_ratio as f32 + 0.5 * self.target_ratio as f32)) .ceil() as usize + POLYNOMIAL_LEN_U; Ok(()) } else { Err(ResampleError::RatioOutOfBounds { provided: new_ratio, original: self.resample_ratio_original, max_relative_ratio: self.max_relative_ratio, }) } } fn set_resample_ratio_relative(&mut self, rel_ratio: f64, ramp: bool) -> ResampleResult<()> { let new_ratio = self.resample_ratio_original * rel_ratio; self.set_resample_ratio(new_ratio, ramp) } fn reset(&mut self) { self.buffer .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero())); self.needed_input_size = (self.chunk_size as f64 / self.resample_ratio_original).ceil() as usize + POLYNOMIAL_LEN_U / 2; self.current_buffer_fill = self.needed_input_size; self.last_index = -(POLYNOMIAL_LEN_I / 2) as f64; self.channel_mask.iter_mut().for_each(|val| *val = true); self.resample_ratio = self.resample_ratio_original; self.target_ratio = self.resample_ratio_original; } } #[cfg(test)] mod tests { use crate::PolynomialDegree; use crate::Resampler; use crate::{check_output, check_ratio}; use crate::{FastFixedIn, FastFixedOut}; use rand::Rng; use test_log::test; #[test] fn make_resampler_fi() { let mut resampler = FastFixedIn::::new(1.2, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); let waves = vec![vec![0.0f64; 1024]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert!( out[0].len() > 1150 && out[0].len() < 1229, "expected {} - {} samples, got {}", 1150, 1229, out[0].len() ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!(out2.len(), 2, "Expected {} channels, got {}", 2, out2.len()); assert!( out2[0].len() > 1226 && out2[0].len() < 1232, "expected {} - {} samples, got {}", 1226, 1232, out2[0].len() ); } #[test] fn reset_resampler_fi() { let mut resampler = FastFixedIn::::new(1.2, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); let mut rng = rand::thread_rng(); let mut waves = vec![vec![0.0f64; 1024]; 2]; waves .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = rng.gen())); let out1 = resampler.process(&waves, None).unwrap(); resampler.reset(); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!( out1, out2, "Resampler gives different output when new and after a reset." ); } #[test] fn make_resampler_fi_32() { let mut resampler = FastFixedIn::::new(1.2, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); let waves = vec![vec![0.0f32; 1024]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert!( out[0].len() > 1150 && out[0].len() < 1229, "expected {} - {} samples, got {}", 1150, 1229, out[0].len() ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!(out2.len(), 2, "Expected {} channels, got {}", 2, out2.len()); assert!( out2[0].len() > 1226 && out2[0].len() < 1232, "expected {} - {} samples, got {}", 1226, 1232, out2[0].len() ); } #[test] fn make_resampler_fi_skipped() { let mut resampler = FastFixedIn::::new(1.2, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); let waves = vec![vec![0.0f64; 1024], Vec::new()]; let mask = vec![true, false]; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert!(out[0].len() > 1150 && out[0].len() < 1250); assert!(out[1].is_empty()); let waves = vec![Vec::new(), vec![0.0f64; 1024]]; let mask = vec![false, true]; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert!(out[1].len() > 1150 && out[0].len() < 1250); assert!(out[0].is_empty()); } #[test] fn make_resampler_fi_downsample() { // Replicate settings from reported issue. let mut resampler = FastFixedIn::::new( 16000 as f64 / 96000 as f64, 1.0, PolynomialDegree::Cubic, 1024, 2, ) .unwrap(); let waves = vec![vec![0.0f64; 1024]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert!( out[0].len() > 140 && out[0].len() < 200, "expected {} - {} samples, got {}", 140, 200, out[0].len() ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!(out2.len(), 2, "Expected {} channels, got {}", 2, out2.len()); assert!( out2[0].len() > 167 && out2[0].len() < 173, "expected {} - {} samples, got {}", 167, 173, out2[0].len() ); } #[test] fn make_resampler_fi_upsample() { // Replicate settings from reported issue. let mut resampler = FastFixedIn::::new( 192000 as f64 / 44100 as f64, 1.0, PolynomialDegree::Cubic, 1024, 2, ) .unwrap(); let waves = vec![vec![0.0f64; 1024]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert!( out[0].len() > 3800 && out[0].len() < 4458, "expected {} - {} samples, got {}", 3800, 4458, out[0].len() ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!(out2.len(), 2, "Expected {} channels, got {}", 2, out2.len()); assert!( out2[0].len() > 4455 && out2[0].len() < 4461, "expected {} - {} samples, got {}", 4455, 4461, out2[0].len() ); } #[test] fn make_resampler_fo() { let mut resampler = FastFixedOut::::new(1.2, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); assert!(frames > 800 && frames < 900); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 1024); } #[test] fn reset_resampler_fo() { let mut resampler = FastFixedOut::::new(1.2, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); let mut rng = rand::thread_rng(); let mut waves = vec![vec![0.0f64; frames]; 2]; waves .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = rng.gen())); let out1 = resampler.process(&waves, None).unwrap(); resampler.reset(); assert_eq!( frames, resampler.input_frames_next(), "Resampler requires different number of frames when new and after a reset." ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!( out1, out2, "Resampler gives different output when new and after a reset." ); } #[test] fn make_resampler_fo_32() { let mut resampler = FastFixedOut::::new(1.2, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); assert!(frames > 800 && frames < 900); let waves = vec![vec![0.0f32; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 1024); } #[test] fn make_resampler_fo_skipped() { let mut resampler = FastFixedOut::::new(1.2, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); assert!(frames > 800 && frames < 900); let mut waves = vec![vec![0.0f64; frames], Vec::new()]; let mask = vec![true, false]; waves[0][100] = 3.0; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 1024); assert!(out[1].is_empty()); println!("{:?}", out[0]); let summed = out[0].iter().sum::(); println!("sum: {}", summed); assert!(summed < 4.0); assert!(summed > 2.0); let frames = resampler.input_frames_next(); let mut waves = vec![Vec::new(), vec![0.0f64; frames]]; let mask = vec![false, true]; waves[1][10] = 3.0; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[1].len(), 1024); assert!(out[0].is_empty()); let summed = out[1].iter().sum::(); assert!(summed < 4.0); assert!(summed > 2.0); } #[test] fn make_resampler_fo_downsample() { let mut resampler = FastFixedOut::::new(0.125, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); assert!( frames > 8192 && frames < 9000, "expected {}..{} samples, got {}", 8192, 9000, frames ); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert_eq!( out[0].len(), 1024, "Expected {} frames, got {}", 1024, out[0].len() ); let frames2 = resampler.input_frames_next(); assert!( frames2 > 8189 && frames2 < 8195, "expected {}..{} samples, got {}", 8189, 8195, frames2 ); let waves2 = vec![vec![0.0f64; frames2]; 2]; let out2 = resampler.process(&waves2, None).unwrap(); assert_eq!( out2[0].len(), 1024, "Expected {} frames, got {}", 1024, out2[0].len() ); } #[test] fn make_resampler_fo_upsample() { let mut resampler = FastFixedOut::::new(8.0, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); assert!( frames > 128 && frames < 300, "expected {}..{} samples, got {}", 140, 200, frames ); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert_eq!( out[0].len(), 1024, "Expected {} frames, got {}", 1024, out[0].len() ); let frames2 = resampler.input_frames_next(); assert!( frames2 > 125 && frames2 < 131, "expected {}..{} samples, got {}", 125, 131, frames2 ); let waves2 = vec![vec![0.0f64; frames2]; 2]; let out2 = resampler.process(&waves2, None).unwrap(); assert_eq!( out2[0].len(), 1024, "Expected {} frames, got {}", 1024, out2[0].len() ); } #[test] fn check_fo_output_up() { let mut resampler = FastFixedOut::::new(8.0, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); check_output!(resampler); } #[test] fn check_fo_output_down() { let mut resampler = FastFixedOut::::new(0.8, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); check_output!(resampler); } #[test] fn check_fi_output_up() { let mut resampler = FastFixedIn::::new(8.0, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); check_output!(resampler); } #[test] fn check_fi_output_down() { let mut resampler = FastFixedIn::::new(0.8, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); check_output!(resampler); } #[test] fn resample_small_fo_up() { let ratio = 96000.0 / 44100.0; let mut resampler = FastFixedOut::::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap(); check_ratio!(resampler, ratio, 1000000); } #[test] fn resample_big_fo_up() { let ratio = 96000.0 / 44100.0; let mut resampler = FastFixedOut::::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); check_ratio!(resampler, ratio, 1000); } #[test] fn resample_small_fo_down() { let ratio = 44100.0 / 96000.0; let mut resampler = FastFixedOut::::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap(); check_ratio!(resampler, ratio, 1000000); } #[test] fn resample_big_fo_down() { let ratio = 44100.0 / 96000.0; let mut resampler = FastFixedOut::::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); check_ratio!(resampler, ratio, 1000); } #[test] fn resample_small_fi_up() { let ratio = 96000.0 / 44100.0; let mut resampler = FastFixedIn::::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap(); check_ratio!(resampler, ratio, 1000000); } #[test] fn resample_big_fi_up() { let ratio = 96000.0 / 44100.0; let mut resampler = FastFixedIn::::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); check_ratio!(resampler, ratio, 1000); } #[test] fn resample_small_fi_down() { let ratio = 44100.0 / 96000.0; let mut resampler = FastFixedIn::::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap(); check_ratio!(resampler, ratio, 1000000); } #[test] fn resample_big_fi_down() { let ratio = 44100.0 / 96000.0; let mut resampler = FastFixedIn::::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); check_ratio!(resampler, ratio, 1000); } } rubato-0.16.2/src/asynchro_sinc.rs000064400000000000000000001543641046102023000152220ustar 00000000000000use crate::error::{ResampleError, ResampleResult, ResamplerConstructionError}; use crate::interpolation::*; #[cfg(target_arch = "x86_64")] use crate::sinc_interpolator::sinc_interpolator_avx::AvxInterpolator; #[cfg(target_arch = "aarch64")] use crate::sinc_interpolator::sinc_interpolator_neon::NeonInterpolator; #[cfg(target_arch = "x86_64")] use crate::sinc_interpolator::sinc_interpolator_sse::SseInterpolator; use crate::sinc_interpolator::{ScalarInterpolator, SincInterpolator}; use crate::windows::WindowFunction; use crate::{update_mask_from_buffers, validate_buffers, Resampler, Sample}; /// A struct holding the parameters for sinc interpolation. #[derive(Debug)] pub struct SincInterpolationParameters { /// Length of the windowed sinc interpolation filter. /// Higher values can allow a higher cut-off frequency leading to less high frequency roll-off /// at the expense of higher cpu usage. 256 is a good starting point. /// The value will be rounded up to the nearest multiple of 8. pub sinc_len: usize, /// Relative cutoff frequency of the sinc interpolation filter /// (relative to the lowest one of fs_in/2 or fs_out/2). Start at 0.95, and increase if needed. pub f_cutoff: f32, /// The number of intermediate points to use for interpolation. /// Higher values use more memory for storing the sinc filters. /// Only the points actually needed are calculated during processing /// so a larger number does not directly lead to higher cpu usage. /// A lower value helps in keeping the sincs in the cpu cache. Start at 128. pub oversampling_factor: usize, /// Interpolation type, see `SincInterpolationType` pub interpolation: SincInterpolationType, /// Window function to use. pub window: WindowFunction, } /// Interpolation methods that can be selected. For asynchronous interpolation where the /// ratio between input and output sample rates can be any number, it's not possible to /// pre-calculate all the needed interpolation filters. /// Instead they have to be computed as needed, which becomes impractical since the /// sincs are very expensive to generate in terms of cpu time. /// It's more efficient to combine the sinc filters with some other interpolation technique. /// Then, sinc filters are used to provide a fixed number of interpolated points between input samples, /// and then, the new value is calculated by interpolation between those points. #[derive(Debug)] pub enum SincInterpolationType { /// For cubic interpolation, the four nearest intermediate points are calculated /// using sinc interpolation. /// Then, a cubic polynomial is fitted to these points, and is used to calculate the new sample value. /// The computation time is approximately twice as long as that of linear interpolation, /// but it requires much fewer intermediate points for a good result. Cubic, /// For quadratic interpolation, the three nearest intermediate points are calculated /// using sinc interpolation. /// Then, a quadratic polynomial is fitted to these points, and is used to calculate the new sample value. /// The computation time lies approximately halfway between that of linear and quadratic interpolation. Quadratic, /// For linear interpolation, the new sample value is calculated by linear interpolation /// between the two nearest points. /// This requires two intermediate points to be calculated using sinc interpolation, /// and the output is obtained by taking a weighted average of these two points. /// This is relatively fast, but needs a large number of intermediate points to /// push the resampling artefacts below the noise floor. Linear, /// The Nearest mode doesn't do any interpolation, but simply picks the nearest intermediate point. /// This is useful when the nearest point is actually the correct one, for example when upsampling by a factor 2, /// like 48kHz->96kHz. /// Then, when setting the oversampling_factor to 2 and using Nearest mode, /// no unnecessary computations are performed and the result is equivalent to that of synchronous resampling. /// This also works for other ratios that can be expressed by a fraction. For 44.1kHz -> 48 kHz, /// setting oversampling_factor to 160 gives the desired result (since 48kHz = 160/147 * 44.1kHz). Nearest, } /// An asynchronous resampler that accepts a fixed number of audio frames for input /// and returns a variable number of frames. /// The number of input frames is determined by the chunk size argument to the constructor. /// This value can be changed by the `set_chunk_size()` method, /// to let the resampler process smaller chunks of audio data. /// Note that the chunk size cannot exceed the value given at creation time. /// The maximum value can be retrieved using the `input_size_max()` method, /// and `input_frames_next()` gives the current value. /// /// The resampling is done by creating a number of intermediate points (defined by oversampling_factor) /// by sinc interpolation. The new samples are then calculated by interpolating between these points. /// /// The resampling ratio can be freely adjusted within the range specified to the constructor. /// Adjusting the ratio does not recalculate the sinc functions used by the anti-aliasing filter. /// This causes no issue when increasing the ratio (which slows down the output). /// However, when decreasing more than a few percent (or speeding up the output), /// the filters can no longer suppress all aliasing and this may lead to some artefacts. /// Higher maximum ratios require more memory to be allocated by [Resampler::output_buffer_allocate]. pub struct SincFixedIn { nbr_channels: usize, chunk_size: usize, max_chunk_size: usize, last_index: f64, resample_ratio: f64, resample_ratio_original: f64, target_ratio: f64, max_relative_ratio: f64, interpolator: Box>, buffer: Vec>, interpolation: SincInterpolationType, channel_mask: Vec, } /// An asynchronous resampler that returns a fixed number of audio frames. /// The number of input frames required is given by the /// [input_frames_next](Resampler::input_frames_next) function. /// /// The number of output frames is determined by the chunk size argument to the constructor. /// This value can be changed by the `set_chunk_size()` method, /// to let the resampler process smaller chunks of audio data. /// Note that the chunk size cannot exceed the value given at creation time. /// The maximum value can be retrieved using the `output_size_max()` method, /// and `output_frames_next()` gives the current value. /// /// The resampling is done by creating a number of intermediate points (defined by oversampling_factor) /// by sinc interpolation. The new samples are then calculated by interpolating between these points. /// /// The resampling ratio can be freely adjusted within the range specified to the constructor. /// Adjusting the ratio does not recalculate the sinc functions used by the anti-aliasing filter. /// This causes no issue when increasing the ratio (which slows down the output). /// However when decreasing more than a few percent (i.e. speeding up the output), /// the filters can no longer suppress all aliasing and this may lead to some artefacts. /// Higher maximum ratios require more memory to be allocated by /// [input_buffer_allocate](Resampler::input_buffer_allocate) and an internal buffer. pub struct SincFixedOut { nbr_channels: usize, chunk_size: usize, max_chunk_size: usize, needed_input_size: usize, last_index: f64, current_buffer_fill: usize, resample_ratio: f64, resample_ratio_original: f64, target_ratio: f64, max_relative_ratio: f64, interpolator: Box>, buffer: Vec>, interpolation: SincInterpolationType, channel_mask: Vec, } pub fn make_interpolator( sinc_len: usize, resample_ratio: f64, f_cutoff: f32, oversampling_factor: usize, window: WindowFunction, ) -> Box> where T: Sample, { let sinc_len = 8 * (((sinc_len as f32) / 8.0).ceil() as usize); let f_cutoff = if resample_ratio >= 1.0 { f_cutoff } else { f_cutoff * resample_ratio as f32 }; #[cfg(target_arch = "x86_64")] if let Ok(interpolator) = AvxInterpolator::::new(sinc_len, oversampling_factor, f_cutoff, window) { return Box::new(interpolator); } #[cfg(target_arch = "x86_64")] if let Ok(interpolator) = SseInterpolator::::new(sinc_len, oversampling_factor, f_cutoff, window) { return Box::new(interpolator); } #[cfg(target_arch = "aarch64")] if let Ok(interpolator) = NeonInterpolator::::new(sinc_len, oversampling_factor, f_cutoff, window) { return Box::new(interpolator); } Box::new(ScalarInterpolator::::new( sinc_len, oversampling_factor, f_cutoff, window, )) } /// Perform cubic polynomial interpolation to get value at x. /// Input points are assumed to be at x = -1, 0, 1, 2. fn interp_cubic(x: T, yvals: &[T; 4]) -> T where T: Sample, { let a0 = yvals[1]; let a1 = -(T::one() / T::coerce(3.0)) * yvals[0] - T::coerce(0.5) * yvals[1] + yvals[2] - (T::one() / T::coerce(6.0)) * yvals[3]; let a2 = T::coerce(0.5) * (yvals[0] + yvals[2]) - yvals[1]; let a3 = T::coerce(0.5) * (yvals[1] - yvals[2]) + (T::one() / T::coerce(6.0)) * (yvals[3] - yvals[0]); let x2 = x * x; let x3 = x2 * x; a0 + a1 * x + a2 * x2 + a3 * x3 } /// Perform quadratic polynomial interpolation to get value at x. /// Input points are assumed to be at x = 0, 1, 2. fn interp_quad(x: T, yvals: &[T; 3]) -> T where T: Sample, { let a2 = yvals[0] - T::coerce(2.0) * yvals[1] + yvals[2]; let a1 = -T::coerce(3.0) * yvals[0] + T::coerce(4.0) * yvals[1] - yvals[2]; let a0 = T::coerce(2.0) * yvals[0]; let x2 = x * x; T::coerce(0.5) * (a0 + a1 * x + a2 * x2) } /// Perform linear interpolation between two points at x=0 and x=1. fn interp_lin(x: T, yvals: &[T; 2]) -> T where T: Sample, { yvals[0] + x * (yvals[1] - yvals[0]) } fn validate_ratios( resample_ratio: f64, max_resample_ratio_relative: f64, ) -> Result<(), ResamplerConstructionError> { if resample_ratio <= 0.0 { return Err(ResamplerConstructionError::InvalidRatio(resample_ratio)); } if max_resample_ratio_relative < 1.0 { return Err(ResamplerConstructionError::InvalidRelativeRatio( max_resample_ratio_relative, )); } Ok(()) } impl SincFixedIn where T: Sample, { /// Create a new SincFixedIn. /// /// Parameters are: /// - `resample_ratio`: Starting ratio between output and input sample rates, must be > 0. /// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Resampler::set_resample_ratio] relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio * 10.0` and `resample_ratio / 10.0`. /// - `parameters`: Parameters for interpolation, see `SincInterpolationParameters`. /// - `chunk_size`: Size of input data in frames. /// - `nbr_channels`: Number of channels in input/output. pub fn new( resample_ratio: f64, max_resample_ratio_relative: f64, parameters: SincInterpolationParameters, chunk_size: usize, nbr_channels: usize, ) -> Result { debug!( "Create new SincFixedIn, ratio: {}, chunk_size: {}, channels: {}, parameters: {:?}", resample_ratio, chunk_size, nbr_channels, parameters ); let interpolator = make_interpolator( parameters.sinc_len, resample_ratio, parameters.f_cutoff, parameters.oversampling_factor, parameters.window, ); Self::new_with_interpolator( resample_ratio, max_resample_ratio_relative, parameters.interpolation, interpolator, chunk_size, nbr_channels, ) } /// Create a new SincFixedIn using an existing Interpolator. /// /// Parameters are: /// - `resample_ratio`: Starting ratio between output and input sample rates, must be > 0. /// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Resampler::set_resample_ratio] relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio` * 10.0 and `resample_ratio` / 10.0. /// - `interpolation_type`: Parameters for interpolation, see `SincInterpolationParameters`. /// - `interpolator`: The interpolator to use. /// - `chunk_size`: Size of output data in frames. /// - `nbr_channels`: Number of channels in input/output. pub fn new_with_interpolator( resample_ratio: f64, max_resample_ratio_relative: f64, interpolation_type: SincInterpolationType, interpolator: Box>, chunk_size: usize, nbr_channels: usize, ) -> Result { validate_ratios(resample_ratio, max_resample_ratio_relative)?; let buffer = vec![vec![T::zero(); chunk_size + 2 * interpolator.len()]; nbr_channels]; let channel_mask = vec![true; nbr_channels]; Ok(SincFixedIn { nbr_channels, chunk_size, max_chunk_size: chunk_size, last_index: -((interpolator.len() / 2) as f64), resample_ratio, resample_ratio_original: resample_ratio, target_ratio: resample_ratio, max_relative_ratio: max_resample_ratio_relative, interpolator, buffer, interpolation: interpolation_type, channel_mask, }) } fn calc_needed_len(&self) -> usize { (self.chunk_size as f64 * (0.5 * self.resample_ratio + 0.5 * self.target_ratio) + 10.0) as usize } } impl Resampler for SincFixedIn where T: Sample, { fn process_into_buffer, Vout: AsMut<[T]>>( &mut self, wave_in: &[Vin], wave_out: &mut [Vout], active_channels_mask: Option<&[bool]>, ) -> ResampleResult<(usize, usize)> { if let Some(mask) = active_channels_mask { self.channel_mask.copy_from_slice(mask); } else { update_mask_from_buffers(&mut self.channel_mask); }; // Set length to chunksize*ratio plus a safety margin of 10 elements. let needed_len = self.calc_needed_len(); validate_buffers( wave_in, wave_out, &self.channel_mask, self.nbr_channels, self.chunk_size, needed_len, )?; let sinc_len = self.interpolator.len(); let oversampling_factor = self.interpolator.nbr_sincs(); let mut t_ratio = 1.0 / self.resample_ratio; let t_ratio_end = 1.0 / self.target_ratio; let approximate_nbr_frames = self.chunk_size as f64 * (0.5 * self.resample_ratio + 0.5 * self.target_ratio); let t_ratio_increment = (t_ratio_end - t_ratio) / approximate_nbr_frames; let end_idx = self.chunk_size as isize - (sinc_len as isize + 1) - t_ratio_end.ceil() as isize; // Update buffer with new data. for buf in self.buffer.iter_mut() { buf.copy_within(self.chunk_size..self.chunk_size + 2 * sinc_len, 0); } for (chan, active) in self.channel_mask.iter().enumerate() { if *active { debug_assert!(needed_len <= wave_out[chan].as_mut().len()); self.buffer[chan][2 * sinc_len..2 * sinc_len + self.chunk_size] .copy_from_slice(&wave_in[chan].as_ref()[..self.chunk_size]); } } let mut idx = self.last_index; let mut n = 0; match self.interpolation { SincInterpolationType::Cubic => { let mut points = [T::zero(); 4]; let mut nearest = [(0isize, 0isize); 4]; while idx < end_idx as f64 { t_ratio += t_ratio_increment; idx += t_ratio; get_nearest_times_4(idx, oversampling_factor as isize, &mut nearest); let frac = idx * oversampling_factor as f64 - (idx * oversampling_factor as f64).floor(); let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { let buf = &self.buffer[chan]; for (n, p) in nearest.iter().zip(points.iter_mut()) { *p = self.interpolator.get_sinc_interpolated( buf, (n.0 + 2 * sinc_len as isize) as usize, n.1 as usize, ); } wave_out[chan].as_mut()[n] = interp_cubic(frac_offset, &points); } } n += 1; } } SincInterpolationType::Quadratic => { let mut points = [T::zero(); 3]; let mut nearest = [(0isize, 0isize); 3]; while idx < end_idx as f64 { t_ratio += t_ratio_increment; idx += t_ratio; get_nearest_times_3(idx, oversampling_factor as isize, &mut nearest); let frac = idx * oversampling_factor as f64 - (idx * oversampling_factor as f64).floor(); let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { let buf = &self.buffer[chan]; for (n, p) in nearest.iter().zip(points.iter_mut()) { *p = self.interpolator.get_sinc_interpolated( buf, (n.0 + 2 * sinc_len as isize) as usize, n.1 as usize, ); } wave_out[chan].as_mut()[n] = interp_quad(frac_offset, &points); } } n += 1; } } SincInterpolationType::Linear => { let mut points = [T::zero(); 2]; let mut nearest = [(0isize, 0isize); 2]; while idx < end_idx as f64 { t_ratio += t_ratio_increment; idx += t_ratio; get_nearest_times_2(idx, oversampling_factor as isize, &mut nearest); let frac = idx * oversampling_factor as f64 - (idx * oversampling_factor as f64).floor(); let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { let buf = &self.buffer[chan]; for (n, p) in nearest.iter().zip(points.iter_mut()) { *p = self.interpolator.get_sinc_interpolated( buf, (n.0 + 2 * sinc_len as isize) as usize, n.1 as usize, ); } wave_out[chan].as_mut()[n] = interp_lin(frac_offset, &points); } } n += 1; } } SincInterpolationType::Nearest => { let mut point; let mut nearest; while idx < end_idx as f64 { t_ratio += t_ratio_increment; idx += t_ratio; nearest = get_nearest_time(idx, oversampling_factor as isize); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { let buf = &self.buffer[chan]; point = self.interpolator.get_sinc_interpolated( buf, (nearest.0 + 2 * sinc_len as isize) as usize, nearest.1 as usize, ); wave_out[chan].as_mut()[n] = point; } } n += 1; } } } // Store last index for next iteration. self.last_index = idx - self.chunk_size as f64; self.resample_ratio = self.target_ratio; trace!( "Resampling channels {:?}, {} frames in, {} frames out", active_channels_mask, self.chunk_size, n, ); Ok((self.chunk_size, n)) } fn output_frames_max(&self) -> usize { // Set length to chunksize*ratio plus a safety margin of 10 elements. (self.max_chunk_size as f64 * self.resample_ratio_original * self.max_relative_ratio + 10.0) as usize } fn output_frames_next(&self) -> usize { self.calc_needed_len() } fn output_delay(&self) -> usize { (self.interpolator.len() as f64 * self.resample_ratio / 2.0) as usize } fn nbr_channels(&self) -> usize { self.nbr_channels } fn input_frames_max(&self) -> usize { self.max_chunk_size } fn input_frames_next(&self) -> usize { self.chunk_size } fn set_resample_ratio(&mut self, new_ratio: f64, ramp: bool) -> ResampleResult<()> { trace!("Change resample ratio to {}", new_ratio); if (new_ratio / self.resample_ratio_original >= 1.0 / self.max_relative_ratio) && (new_ratio / self.resample_ratio_original <= self.max_relative_ratio) { if !ramp { self.resample_ratio = new_ratio; } self.target_ratio = new_ratio; Ok(()) } else { Err(ResampleError::RatioOutOfBounds { provided: new_ratio, original: self.resample_ratio_original, max_relative_ratio: self.max_relative_ratio, }) } } fn set_resample_ratio_relative(&mut self, rel_ratio: f64, ramp: bool) -> ResampleResult<()> { let new_ratio = self.resample_ratio_original * rel_ratio; self.set_resample_ratio(new_ratio, ramp) } fn reset(&mut self) { self.buffer .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero())); self.channel_mask.iter_mut().for_each(|val| *val = true); self.last_index = -((self.interpolator.len() / 2) as f64); self.resample_ratio = self.resample_ratio_original; self.target_ratio = self.resample_ratio_original; self.chunk_size = self.max_chunk_size; } fn set_chunk_size(&mut self, chunksize: usize) -> ResampleResult<()> { if chunksize > self.max_chunk_size || chunksize == 0 { return Err(ResampleError::InvalidChunkSize { max: self.max_chunk_size, requested: chunksize, }); } self.chunk_size = chunksize; Ok(()) } } impl SincFixedOut where T: Sample, { /// Create a new SincFixedOut. /// /// Parameters are: /// - `resample_ratio`: Starting ratio between output and input sample rates, must be > 0. /// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Resampler::set_resample_ratio] relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio * 10.0` and `resample_ratio / 10.0`. /// - `parameters`: Parameters for interpolation, see `SincInterpolationParameters`. /// - `chunk_size`: Size of output data in frames. /// - `nbr_channels`: Number of channels in input/output. pub fn new( resample_ratio: f64, max_resample_ratio_relative: f64, parameters: SincInterpolationParameters, chunk_size: usize, nbr_channels: usize, ) -> Result { debug!( "Create new SincFixedIn, ratio: {}, chunk_size: {}, channels: {}, parameters: {:?}", resample_ratio, chunk_size, nbr_channels, parameters ); let interpolator = make_interpolator( parameters.sinc_len, resample_ratio, parameters.f_cutoff, parameters.oversampling_factor, parameters.window, ); Self::new_with_interpolator( resample_ratio, max_resample_ratio_relative, parameters.interpolation, interpolator, chunk_size, nbr_channels, ) } /// Create a new SincFixedOut using an existing Interpolator. /// /// Parameters are: /// - `resample_ratio`: Starting ratio between output and input sample rates, must be > 0. /// - `max_resample_ratio_relative`: Maximum ratio that can be set with [Resampler::set_resample_ratio] relative to `resample_ratio`, must be >= 1.0. The minimum relative ratio is the reciprocal of the maximum. For example, with `max_resample_ratio_relative` of 10.0, the ratio can be set between `resample_ratio` * 10.0 and `resample_ratio` / 10.0. /// - `interpolation_type`: Parameters for interpolation, see `SincInterpolationParameters`. /// - `interpolator`: The interpolator to use. /// - `chunk_size`: Size of output data in frames. /// - `nbr_channels`: Number of channels in input/output. pub fn new_with_interpolator( resample_ratio: f64, max_resample_ratio_relative: f64, interpolation_type: SincInterpolationType, interpolator: Box>, chunk_size: usize, nbr_channels: usize, ) -> Result { validate_ratios(resample_ratio, max_resample_ratio_relative)?; let needed_input_size = (chunk_size as f64 / resample_ratio).ceil() as usize + interpolator.len() / 2; let buffer_channel_length = ((max_resample_ratio_relative + 1.0) * needed_input_size as f64) as usize + 2 * interpolator.len(); let buffer = vec![vec![T::zero(); buffer_channel_length]; nbr_channels]; let channel_mask = vec![true; nbr_channels]; Ok(SincFixedOut { nbr_channels, chunk_size, max_chunk_size: chunk_size, needed_input_size, last_index: -((interpolator.len() / 2) as f64), current_buffer_fill: needed_input_size, resample_ratio, resample_ratio_original: resample_ratio, target_ratio: resample_ratio, max_relative_ratio: max_resample_ratio_relative, interpolator, buffer, interpolation: interpolation_type, channel_mask, }) } fn update_needed_len(&mut self) { self.needed_input_size = (self.last_index as f32 + self.chunk_size as f32 / (0.5 * self.resample_ratio as f32 + 0.5 * self.target_ratio as f32) + self.interpolator.len() as f32) .ceil() as usize; } } impl Resampler for SincFixedOut where T: Sample, { fn process_into_buffer, Vout: AsMut<[T]>>( &mut self, wave_in: &[Vin], wave_out: &mut [Vout], active_channels_mask: Option<&[bool]>, ) -> ResampleResult<(usize, usize)> { if let Some(mask) = active_channels_mask { self.channel_mask.copy_from_slice(mask); } else { update_mask_from_buffers(&mut self.channel_mask); }; validate_buffers( wave_in, wave_out, &self.channel_mask, self.nbr_channels, self.needed_input_size, self.chunk_size, )?; let sinc_len = self.interpolator.len(); let oversampling_factor = self.interpolator.nbr_sincs(); for buf in self.buffer.iter_mut() { buf.copy_within( self.current_buffer_fill..self.current_buffer_fill + 2 * sinc_len, 0, ); } self.current_buffer_fill = self.needed_input_size; for (chan, active) in self.channel_mask.iter().enumerate() { if *active { debug_assert!(self.chunk_size <= wave_out[chan].as_mut().len()); self.buffer[chan][2 * sinc_len..2 * sinc_len + self.needed_input_size] .copy_from_slice(&wave_in[chan].as_ref()[..self.needed_input_size]); } } let mut idx = self.last_index; let mut t_ratio = 1.0 / self.resample_ratio; let t_ratio_end = 1.0 / self.target_ratio; let t_ratio_increment = (t_ratio_end - t_ratio) / self.chunk_size as f64; match self.interpolation { SincInterpolationType::Cubic => { let mut points = [T::zero(); 4]; let mut nearest = [(0isize, 0isize); 4]; for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; get_nearest_times_4(idx, oversampling_factor as isize, &mut nearest); let frac = idx * oversampling_factor as f64 - (idx * oversampling_factor as f64).floor(); let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { let buf = &self.buffer[chan]; for (n, p) in nearest.iter().zip(points.iter_mut()) { *p = self.interpolator.get_sinc_interpolated( buf, (n.0 + 2 * sinc_len as isize) as usize, n.1 as usize, ); } wave_out[chan].as_mut()[frame] = interp_cubic(frac_offset, &points); } } } } SincInterpolationType::Quadratic => { let mut points = [T::zero(); 3]; let mut nearest = [(0isize, 0isize); 3]; for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; get_nearest_times_3(idx, oversampling_factor as isize, &mut nearest); let frac = idx * oversampling_factor as f64 - (idx * oversampling_factor as f64).floor(); let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { let buf = &self.buffer[chan]; for (n, p) in nearest.iter().zip(points.iter_mut()) { *p = self.interpolator.get_sinc_interpolated( buf, (n.0 + 2 * sinc_len as isize) as usize, n.1 as usize, ); } wave_out[chan].as_mut()[frame] = interp_quad(frac_offset, &points); } } } } SincInterpolationType::Linear => { let mut points = [T::zero(); 2]; let mut nearest = [(0isize, 0isize); 2]; for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; get_nearest_times_2(idx, oversampling_factor as isize, &mut nearest); let frac = idx * oversampling_factor as f64 - (idx * oversampling_factor as f64).floor(); let frac_offset = T::coerce(frac); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { let buf = &self.buffer[chan]; for (n, p) in nearest.iter().zip(points.iter_mut()) { *p = self.interpolator.get_sinc_interpolated( buf, (n.0 + 2 * sinc_len as isize) as usize, n.1 as usize, ); } wave_out[chan].as_mut()[frame] = interp_lin(frac_offset, &points); } } } } SincInterpolationType::Nearest => { let mut point; let mut nearest; for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; nearest = get_nearest_time(idx, oversampling_factor as isize); for (chan, active) in self.channel_mask.iter().enumerate() { if *active { let buf = &self.buffer[chan]; point = self.interpolator.get_sinc_interpolated( buf, (nearest.0 + 2 * sinc_len as isize) as usize, nearest.1 as usize, ); wave_out[chan].as_mut()[frame] = point; } } } } } // Store last index for next iteration. let input_frames_used = self.needed_input_size; self.last_index = idx - self.current_buffer_fill as f64; self.resample_ratio = self.target_ratio; self.update_needed_len(); trace!( "Resampling channels {:?}, {} frames in, {} frames out. Next needed length: {} frames, last index {}", active_channels_mask, self.current_buffer_fill, self.chunk_size, self.needed_input_size, self.last_index ); Ok((input_frames_used, self.chunk_size)) } fn input_frames_max(&self) -> usize { (self.max_chunk_size as f64 / self.resample_ratio_original * self.max_relative_ratio).ceil() as usize + 2 + self.interpolator.len() / 2 } fn input_frames_next(&self) -> usize { self.needed_input_size } fn nbr_channels(&self) -> usize { self.nbr_channels } fn output_frames_max(&self) -> usize { self.max_chunk_size } fn output_frames_next(&self) -> usize { self.chunk_size } fn output_delay(&self) -> usize { (self.interpolator.len() as f64 * self.resample_ratio / 2.0) as usize } fn set_resample_ratio(&mut self, new_ratio: f64, ramp: bool) -> ResampleResult<()> { trace!("Change resample ratio to {}", new_ratio); if (new_ratio / self.resample_ratio_original >= 1.0 / self.max_relative_ratio) && (new_ratio / self.resample_ratio_original <= self.max_relative_ratio) { if !ramp { self.resample_ratio = new_ratio; } self.target_ratio = new_ratio; self.update_needed_len(); Ok(()) } else { Err(ResampleError::RatioOutOfBounds { provided: new_ratio, original: self.resample_ratio_original, max_relative_ratio: self.max_relative_ratio, }) } } fn set_resample_ratio_relative(&mut self, rel_ratio: f64, ramp: bool) -> ResampleResult<()> { let new_ratio = self.resample_ratio_original * rel_ratio; self.set_resample_ratio(new_ratio, ramp) } fn reset(&mut self) { self.buffer .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero())); self.resample_ratio = self.resample_ratio_original; self.target_ratio = self.resample_ratio_original; self.last_index = -((self.interpolator.len() / 2) as f64); self.chunk_size = self.max_chunk_size; self.update_needed_len(); self.current_buffer_fill = self.needed_input_size; self.channel_mask.iter_mut().for_each(|val| *val = true); } fn set_chunk_size(&mut self, chunksize: usize) -> ResampleResult<()> { if chunksize > self.max_chunk_size || chunksize == 0 { return Err(ResampleError::InvalidChunkSize { max: self.max_chunk_size, requested: chunksize, }); } self.chunk_size = chunksize; self.update_needed_len(); Ok(()) } } #[cfg(test)] mod tests { use super::{interp_cubic, interp_lin}; use crate::Resampler; use crate::SincInterpolationParameters; use crate::SincInterpolationType; use crate::WindowFunction; use crate::{check_output, check_ratio}; use crate::{SincFixedIn, SincFixedOut}; use rand::Rng; use test_log::test; fn basic_params() -> SincInterpolationParameters { SincInterpolationParameters { sinc_len: 64, f_cutoff: 0.95, interpolation: SincInterpolationType::Cubic, oversampling_factor: 16, window: WindowFunction::BlackmanHarris2, } } #[test] fn int_cubic() { let params = basic_params(); let _resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let yvals = [0.0f64, 2.0f64, 4.0f64, 6.0f64]; let interp = interp_cubic(0.5f64, &yvals); assert_eq!(interp, 3.0f64); } #[test] fn int_lin_32() { let params = basic_params(); let _resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let yvals = [1.0f32, 5.0f32]; let interp = interp_lin(0.25f32, &yvals); assert_eq!(interp, 2.0f32); } #[test] fn int_cubic_32() { let params = basic_params(); let _resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let yvals = [0.0f32, 2.0f32, 4.0f32, 6.0f32]; let interp = interp_cubic(0.5f32, &yvals); assert_eq!(interp, 3.0f32); } #[test] fn int_lin() { let params = basic_params(); let _resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let yvals = [1.0f64, 5.0f64]; let interp = interp_lin(0.25f64, &yvals); assert_eq!(interp, 2.0f64); } #[test] fn make_resampler_fi() { let params = basic_params(); let mut resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let waves = vec![vec![0.0f64; 1024]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert!( out[0].len() > 1150 && out[0].len() < 1229, "expected {} - {} samples, got {}", 1150, 1229, out[0].len() ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!(out2.len(), 2, "Expected {} channels, got {}", 2, out2.len()); assert!( out2[0].len() > 1226 && out2[0].len() < 1232, "expected {} - {} samples, got {}", 1226, 1232, out2[0].len() ); } #[test] fn reset_resampler_fi() { let params = basic_params(); let mut resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let mut rng = rand::thread_rng(); let mut waves = vec![vec![0.0f64; 1024]; 2]; waves .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = rng.gen())); let out1 = resampler.process(&waves, None).unwrap(); resampler.reset(); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!( out1, out2, "Resampler gives different output when new and after a reset." ); } #[test] fn make_resampler_fi_32() { let params = basic_params(); let mut resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let waves = vec![vec![0.0f32; 1024]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert!( out[0].len() > 1150 && out[0].len() < 1229, "expected {} - {} samples, got {}", 1150, 1229, out[0].len() ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!(out2.len(), 2, "Expected {} channels, got {}", 2, out2.len()); assert!( out2[0].len() > 1226 && out2[0].len() < 1232, "expected {} - {} samples, got {}", 1226, 1232, out2[0].len() ); } #[test] fn make_resampler_fi_skipped() { let params = basic_params(); let mut resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let waves = vec![vec![0.0f64; 1024], Vec::new()]; let mask = vec![true, false]; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert!(out[0].len() > 1150 && out[0].len() < 1250); assert!(out[1].is_empty()); let waves = vec![Vec::new(), vec![0.0f64; 1024]]; let mask = vec![false, true]; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert!(out[1].len() > 1150 && out[0].len() < 1250); assert!(out[0].is_empty()); } #[test] fn make_resampler_fi_downsample() { // Replicate settings from reported issue let params = SincInterpolationParameters { sinc_len: 256, f_cutoff: 0.95, interpolation: SincInterpolationType::Cubic, oversampling_factor: 160, window: WindowFunction::BlackmanHarris2, }; let mut resampler = SincFixedIn::::new(16000 as f64 / 96000 as f64, 1.0, params, 1024, 2).unwrap(); let waves = vec![vec![0.0f64; 1024]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert!( out[0].len() > 140 && out[0].len() < 200, "expected {} - {} samples, got {}", 140, 200, out[0].len() ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!(out2.len(), 2, "Expected {} channels, got {}", 2, out2.len()); assert!( out2[0].len() > 167 && out2[0].len() < 173, "expected {} - {} samples, got {}", 167, 173, out2[0].len() ); } #[test] fn make_resampler_fi_upsample() { // Replicate settings from reported issue let params = SincInterpolationParameters { sinc_len: 256, f_cutoff: 0.95, interpolation: SincInterpolationType::Cubic, oversampling_factor: 160, window: WindowFunction::BlackmanHarris2, }; let mut resampler = SincFixedIn::::new(192000 as f64 / 44100 as f64, 1.0, params, 1024, 2).unwrap(); let waves = vec![vec![0.0f64; 1024]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert!( out[0].len() > 3800 && out[0].len() < 4458, "expected {} - {} samples, got {}", 3800, 4458, out[0].len() ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!(out2.len(), 2, "Expected {} channels, got {}", 2, out2.len()); assert!( out2[0].len() > 4455 && out2[0].len() < 4461, "expected {} - {} samples, got {}", 4455, 4461, out2[0].len() ); } #[test] fn make_resampler_fo() { let params = basic_params(); let mut resampler = SincFixedOut::::new(1.2, 1.0, params, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); assert!(frames > 800 && frames < 900); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 1024); } #[test] fn reset_resampler_fo() { let params = basic_params(); let mut resampler = SincFixedOut::::new(1.2, 1.0, params, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); let mut rng = rand::thread_rng(); let mut waves = vec![vec![0.0f64; frames]; 2]; waves .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = rng.gen())); let out1 = resampler.process(&waves, None).unwrap(); resampler.reset(); assert_eq!( frames, resampler.input_frames_next(), "Resampler requires different number of frames when new and after a reset." ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!( out1, out2, "Resampler gives different output when new and after a reset." ); } #[test] fn make_resampler_fo_32() { let params = basic_params(); let mut resampler = SincFixedOut::::new(1.2, 1.0, params, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); assert!(frames > 800 && frames < 900); let waves = vec![vec![0.0f32; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 1024); } #[test] fn make_resampler_fo_skipped() { let params = basic_params(); let mut resampler = SincFixedOut::::new(1.2, 1.0, params, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); assert!(frames > 800 && frames < 900); let mut waves = vec![vec![0.0f64; frames], Vec::new()]; let mask = vec![true, false]; waves[0][100] = 3.0; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 1024); assert!(out[1].is_empty()); println!("{:?}", out[0]); let summed = out[0].iter().sum::(); println!("sum: {}", summed); assert!(summed < 4.0); assert!(summed > 2.0); let frames = resampler.input_frames_next(); let mut waves = vec![Vec::new(), vec![0.0f64; frames]]; let mask = vec![false, true]; waves[1][10] = 3.0; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[1].len(), 1024); assert!(out[0].is_empty()); let summed = out[1].iter().sum::(); assert!(summed < 4.0); assert!(summed > 2.0); } #[test] fn make_resampler_fo_downsample() { let params = SincInterpolationParameters { sinc_len: 256, f_cutoff: 0.95, interpolation: SincInterpolationType::Cubic, oversampling_factor: 160, window: WindowFunction::BlackmanHarris2, }; let mut resampler = SincFixedOut::::new(0.125, 1.0, params, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); assert!( frames > 8192 && frames < 9000, "expected {}..{} samples, got {}", 8192, 9000, frames ); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert_eq!( out[0].len(), 1024, "Expected {} frames, got {}", 1024, out[0].len() ); let frames2 = resampler.input_frames_next(); assert!( frames2 > 8189 && frames2 < 8195, "expected {}..{} samples, got {}", 8189, 8195, frames2 ); let waves2 = vec![vec![0.0f64; frames2]; 2]; let out2 = resampler.process(&waves2, None).unwrap(); assert_eq!( out2[0].len(), 1024, "Expected {} frames, got {}", 1024, out2[0].len() ); } #[test] fn make_resampler_fo_upsample() { let params = SincInterpolationParameters { sinc_len: 256, f_cutoff: 0.95, interpolation: SincInterpolationType::Cubic, oversampling_factor: 160, window: WindowFunction::BlackmanHarris2, }; let mut resampler = SincFixedOut::::new(8.0, 1.0, params, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); assert!( frames > 128 && frames < 300, "expected {}..{} samples, got {}", 140, 200, frames ); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2, "Expected {} channels, got {}", 2, out.len()); assert_eq!( out[0].len(), 1024, "Expected {} frames, got {}", 1024, out[0].len() ); let frames2 = resampler.input_frames_next(); assert!( frames2 > 125 && frames2 < 131, "expected {}..{} samples, got {}", 125, 131, frames2 ); let waves2 = vec![vec![0.0f64; frames2]; 2]; let out2 = resampler.process(&waves2, None).unwrap(); assert_eq!( out2[0].len(), 1024, "Expected {} frames, got {}", 1024, out2[0].len() ); } #[test] fn check_fo_output_up() { let params = basic_params(); let mut resampler = SincFixedOut::::new(1.2, 1.0, params, 1024, 2).unwrap(); check_output!(resampler); } #[test] fn check_fo_output_down() { let params = basic_params(); let mut resampler = SincFixedOut::::new(0.8, 1.0, params, 1024, 2).unwrap(); check_output!(resampler); } #[test] fn check_fi_output_up() { let params = basic_params(); let mut resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); check_output!(resampler); } #[test] fn check_fi_output_down() { let params = basic_params(); let mut resampler = SincFixedIn::::new(0.8, 1.0, params, 1024, 2).unwrap(); check_output!(resampler); } #[test] fn resample_small_fo_up() { let ratio = 96000.0 / 44100.0; let params = basic_params(); let mut resampler = SincFixedOut::::new(ratio, 1.0, params, 1, 2).unwrap(); check_ratio!(resampler, ratio, 100000); } #[test] fn resample_big_fo_up() { let ratio = 96000.0 / 44100.0; let params = basic_params(); let mut resampler = SincFixedOut::::new(ratio, 1.0, params, 1024, 2).unwrap(); check_ratio!(resampler, ratio, 100); } #[test] fn resample_small_fo_down() { let ratio = 44100.0 / 96000.0; let params = basic_params(); let mut resampler = SincFixedOut::::new(ratio, 1.0, params, 1, 2).unwrap(); check_ratio!(resampler, ratio, 100000); } #[test] fn resample_big_fo_down() { let ratio = 44100.0 / 96000.0; let params = basic_params(); let mut resampler = SincFixedOut::::new(ratio, 1.0, params, 1024, 2).unwrap(); check_ratio!(resampler, ratio, 100); } #[test] fn resample_small_fi_up() { let ratio = 96000.0 / 44100.0; let params = basic_params(); let mut resampler = SincFixedIn::::new(ratio, 1.0, params, 1, 2).unwrap(); check_ratio!(resampler, ratio, 100000); } #[test] fn resample_big_fi_up() { let ratio = 96000.0 / 44100.0; let params = basic_params(); let mut resampler = SincFixedIn::::new(ratio, 1.0, params, 1024, 2).unwrap(); check_ratio!(resampler, ratio, 100); } #[test] fn resample_small_fi_down() { let ratio = 44100.0 / 96000.0; let params = basic_params(); let mut resampler = SincFixedIn::::new(ratio, 1.0, params, 1, 2).unwrap(); check_ratio!(resampler, ratio, 100000); } #[test] fn resample_big_fi_down() { let ratio = 44100.0 / 96000.0; let params = basic_params(); let mut resampler = SincFixedIn::::new(ratio, 1.0, params, 1024, 2).unwrap(); check_ratio!(resampler, ratio, 100); } #[test] fn check_fo_output_resize() { let params = basic_params(); let mut resampler = SincFixedOut::::new(1.2, 1.0, params, 1024, 2).unwrap(); assert_eq!(resampler.output_frames_next(), 1024); resampler.set_chunk_size(256).unwrap(); assert_eq!(resampler.output_frames_next(), 256); check_output!(resampler); } #[test] fn check_fi_output_resize() { let params = basic_params(); let mut resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); assert_eq!(resampler.input_frames_next(), 1024); resampler.set_chunk_size(256).unwrap(); assert_eq!(resampler.input_frames_next(), 256); check_output!(resampler); } } rubato-0.16.2/src/error.rs000064400000000000000000000174721046102023000135070ustar 00000000000000use std::error; use std::fmt; /// An identifier for a cpu feature. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CpuFeature { /// x86 sse3 cpu feature. #[cfg(target_arch = "x86_64")] Sse3, /// x86_64 avx cpu feature. #[cfg(target_arch = "x86_64")] Avx, /// the fma cpu feature. #[cfg(target_arch = "x86_64")] Fma, /// aarc64 neon cpu feature. #[cfg(target_arch = "aarch64")] Neon, } impl CpuFeature { /// Test if the given CPU feature is detected. pub fn is_detected(&self) -> bool { match *self { #[cfg(target_arch = "x86_64")] CpuFeature::Sse3 => { is_x86_feature_detected!("sse3") } #[cfg(target_arch = "x86_64")] CpuFeature::Avx => { is_x86_feature_detected!("avx") } #[cfg(target_arch = "x86_64")] CpuFeature::Fma => { is_x86_feature_detected!("fma") } #[cfg(target_arch = "aarch64")] CpuFeature::Neon => { std::arch::is_aarch64_feature_detected!("neon") } } } } #[allow(unused_variables)] impl fmt::Display for CpuFeature { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { #[cfg(target_arch = "x86_64")] CpuFeature::Sse3 => { write!(f, "sse3") } #[cfg(target_arch = "x86_64")] CpuFeature::Avx => { write!(f, "avx") } #[cfg(target_arch = "x86_64")] CpuFeature::Fma => { write!(f, "fma") } #[cfg(target_arch = "aarch64")] CpuFeature::Neon => { write!(f, "neon") } } } } /// Error raised when trying to use a CPU feature which is not supported. #[derive(Debug, Clone, Copy)] pub struct MissingCpuFeature(pub(crate) CpuFeature); impl fmt::Display for MissingCpuFeature { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Missing CPU feature `{}`", self.0) } } impl error::Error for MissingCpuFeature {} /// The error type returned when constructing [Resampler](crate::Resampler). pub enum ResamplerConstructionError { InvalidSampleRate { input: usize, output: usize }, InvalidRelativeRatio(f64), InvalidRatio(f64), } impl fmt::Display for ResamplerConstructionError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::InvalidSampleRate{input, output} => write!(formatter, "Input and output sample rates must both be > 0. Provided input: {}, provided output: {}", input, output ), Self::InvalidRatio(provided) => write!(formatter, "Invalid resample_ratio provided: {}. resample_ratio must be > 0", provided ), Self::InvalidRelativeRatio(provided) => write!(formatter, "Invalid max_resample_ratio_relative provided: {}. max_resample_ratio_relative must be >= 1", provided ), } } } impl fmt::Debug for ResamplerConstructionError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!(formatter, "{}", self) } } impl error::Error for ResamplerConstructionError {} /// The error type used by `rubato`. pub enum ResampleError { /// Error raised when [Resampler::set_resample_ratio](crate::Resampler::set_resample_ratio) /// is called with a ratio outside the maximum range specified when /// the resampler was constructed. RatioOutOfBounds { provided: f64, original: f64, max_relative_ratio: f64, }, /// Error raised when calling [Resampler::set_resample_ratio](crate::Resampler::set_resample_ratio) /// on a synchronous resampler. SyncNotAdjustable, /// Error raised when the number of channels in the input buffer doesn't match the value expected. WrongNumberOfInputChannels { expected: usize, actual: usize, }, /// Error raised when the number of channels in the output buffer doesn't match the value expected. WrongNumberOfOutputChannels { expected: usize, actual: usize, }, /// Error raised when the number of channels in the mask doesn't match the value expected. WrongNumberOfMaskChannels { expected: usize, actual: usize, }, /// Error raised when the number of frames in an input channel is less /// than the minimum expected. InsufficientInputBufferSize { channel: usize, expected: usize, actual: usize, }, /// Error raised when the number of frames in an output channel is less /// than the minimum expected. InsufficientOutputBufferSize { channel: usize, expected: usize, actual: usize, }, InvalidChunkSize { max: usize, requested: usize, }, ChunkSizeNotAdjustable, } impl fmt::Display for ResampleError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::RatioOutOfBounds { provided, original, max_relative_ratio, } => { write!(f, "New resample ratio out of bounds. Provided ratio {}, original resample ratio {}, maximum relative ratio {}, allowed absolute range {} to {}", provided, original, max_relative_ratio, original / max_relative_ratio, original * max_relative_ratio) } Self::SyncNotAdjustable { .. } => { write!(f, "Not possible to adjust a synchronous resampler") } Self::WrongNumberOfInputChannels { expected, actual } => { write!( f, "Wrong number of channels {} in input, expected {}", actual, expected ) } Self::WrongNumberOfOutputChannels { expected, actual } => { write!( f, "Wrong number of channels {} in output, expected {}", actual, expected ) } Self::WrongNumberOfMaskChannels { expected, actual } => { write!( f, "Wrong number of channels {} in mask, expected {}", actual, expected ) } Self::InsufficientInputBufferSize { channel, expected, actual, } => { write!( f, "Insufficient buffer size {} for input channel {}, expected {}", actual, channel, expected ) } Self::InsufficientOutputBufferSize { channel, expected, actual, } => { write!( f, "Insufficient buffer size {} for output channel {}, expected {}", actual, channel, expected ) } Self::InvalidChunkSize { max, requested } => { write!( f, "Invalid chunk size {}, value must be non-zero and cannot exceed {}", requested, max ) } Self::ChunkSizeNotAdjustable { .. } => { write!(f, "This resampler does not support changing the chunk size") } } } } impl fmt::Debug for ResampleError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!(formatter, "{}", self) } } impl error::Error for ResampleError {} /// A result alias for the error type used by `rubato`. pub type ResampleResult = ::std::result::Result; rubato-0.16.2/src/interpolation.rs000064400000000000000000000076201046102023000152370ustar 00000000000000/// Get the two nearest time points for time t in format (index, subindex). pub fn get_nearest_times_2(t: f64, factor: isize, points: &mut [(isize, isize); 2]) { let mut index = t.floor() as isize; let mut subindex = ((t - t.floor()) * (factor as f64)).floor() as isize; points[0] = (index, subindex); subindex += 1; if subindex >= factor { subindex -= factor; index += 1; } points[1] = (index, subindex); } /// Get the three nearest time points for time t in format (index, subindex). pub fn get_nearest_times_3(t: f64, factor: isize, points: &mut [(isize, isize); 3]) { let start = t.floor() as isize; let frac = ((t - t.floor()) * (factor as f64)).floor() as isize; let mut index; let mut subindex; for (idx, sub) in (0..3).enumerate() { index = start; subindex = frac + sub; if subindex < 0 { subindex += factor; index -= 1; } else if subindex >= factor { subindex -= factor; index += 1; } points[idx] = (index, subindex); } } /// Get the four nearest time points for time t in format (index, subindex). pub fn get_nearest_times_4(t: f64, factor: isize, points: &mut [(isize, isize); 4]) { let start = t.floor() as isize; let frac = ((t - t.floor()) * (factor as f64)).floor() as isize; let mut index; let mut subindex; for (idx, sub) in (-1..3).enumerate() { index = start; subindex = frac + sub; if subindex < 0 { subindex += factor; index -= 1; } else if subindex >= factor { subindex -= factor; index += 1; } points[idx] = (index, subindex); } } /// Get the nearest time point for time t in format (index, subindex). pub fn get_nearest_time(t: f64, factor: isize) -> (isize, isize) { let mut index = t.floor() as isize; let mut subindex = ((t - t.floor()) * (factor as f64)).round() as isize; if subindex >= factor { subindex -= factor; index += 1; } (index, subindex) } #[cfg(test)] mod tests { use crate::interpolation::get_nearest_time; use crate::interpolation::get_nearest_times_2; use crate::interpolation::get_nearest_times_3; use crate::interpolation::get_nearest_times_4; use test_log::test; #[test] fn get_nearest_2() { let t = 5.9f64; let mut times = [(0isize, 0isize); 2]; get_nearest_times_2(t, 8, &mut times); assert_eq!(times[0], (5, 7)); assert_eq!(times[1], (6, 0)); } #[test] fn get_nearest_4() { let t = 5.9f64; let mut times = [(0isize, 0isize); 4]; get_nearest_times_4(t, 8, &mut times); assert_eq!(times[0], (5, 6)); assert_eq!(times[1], (5, 7)); assert_eq!(times[2], (6, 0)); assert_eq!(times[3], (6, 1)); } #[test] fn get_nearest_3() { let t = 5.9f64; let mut times = [(0isize, 0isize); 3]; get_nearest_times_3(t, 8, &mut times); assert_eq!(times[0], (5, 7)); assert_eq!(times[1], (6, 0)); assert_eq!(times[2], (6, 1)); } #[test] fn get_nearest_4_neg() { let t = -5.999f64; let mut times = [(0isize, 0isize); 4]; get_nearest_times_4(t, 8, &mut times); assert_eq!(times[0], (-7, 7)); assert_eq!(times[1], (-6, 0)); assert_eq!(times[2], (-6, 1)); assert_eq!(times[3], (-6, 2)); } #[test] fn get_nearest_4_zero() { let t = -0.00001f64; let mut times = [(0isize, 0isize); 4]; get_nearest_times_4(t, 8, &mut times); assert_eq!(times[0], (-1, 6)); assert_eq!(times[1], (-1, 7)); assert_eq!(times[2], (0, 0)); assert_eq!(times[3], (0, 1)); } #[test] fn get_nearest_single() { let t = 5.5f64; let time = get_nearest_time(t, 8); assert_eq!(time, (5, 4)); } } rubato-0.16.2/src/lib.rs000064400000000000000000000715621046102023000131240ustar 00000000000000#![doc = include_str!("../README.md")] #[cfg(feature = "log")] extern crate log; // Logging wrapper macros to avoid cluttering the code with conditionals. #[allow(unused)] macro_rules! trace { ($($x:tt)*) => ( #[cfg(feature = "log")] { log::trace!($($x)*) } ) } #[allow(unused)] macro_rules! debug { ($($x:tt)*) => ( #[cfg(feature = "log")] { log::debug!($($x)*) } ) } #[allow(unused)] macro_rules! info { ($($x:tt)*) => ( #[cfg(feature = "log")] { log::info!($($x)*) } ) } #[allow(unused)] macro_rules! warn { ($($x:tt)*) => ( #[cfg(feature = "log")] { log::warn!($($x)*) } ) } #[allow(unused)] macro_rules! error { ($($x:tt)*) => ( #[cfg(feature = "log")] { log::error!($($x)*) } ) } mod asynchro_fast; mod asynchro_sinc; mod error; mod interpolation; mod sample; mod sinc; #[cfg(feature = "fft_resampler")] mod synchro; mod windows; pub mod sinc_interpolator; pub use crate::asynchro_fast::{FastFixedIn, FastFixedOut, PolynomialDegree}; pub use crate::asynchro_sinc::{ SincFixedIn, SincFixedOut, SincInterpolationParameters, SincInterpolationType, }; pub use crate::error::{ CpuFeature, MissingCpuFeature, ResampleError, ResampleResult, ResamplerConstructionError, }; pub use crate::sample::Sample; #[cfg(feature = "fft_resampler")] pub use crate::synchro::{FftFixedIn, FftFixedInOut, FftFixedOut}; pub use crate::windows::{calculate_cutoff, WindowFunction}; /// A resampler that is used to resample a chunk of audio to a new sample rate. /// For asynchronous resamplers, the rate can be adjusted as required. /// /// This trait is not object safe. If you need an object safe resampler, /// use the [VecResampler] wrapper trait. pub trait Resampler: Send where T: Sample, { /// This is a convenience wrapper for [process_into_buffer](Resampler::process_into_buffer) /// that allocates the output buffer with each call. For realtime applications, use /// [process_into_buffer](Resampler::process_into_buffer) with a buffer allocated by /// [output_buffer_allocate](Resampler::output_buffer_allocate) instead of this function. fn process>( &mut self, wave_in: &[V], active_channels_mask: Option<&[bool]>, ) -> ResampleResult>> { let frames = self.output_frames_next(); let channels = self.nbr_channels(); let mut wave_out = Vec::with_capacity(channels); for chan in 0..channels { let chan_out = if active_channels_mask.map(|mask| mask[chan]).unwrap_or(true) { vec![T::zero(); frames] } else { vec![] }; wave_out.push(chan_out); } let (_, out_len) = self.process_into_buffer(wave_in, &mut wave_out, active_channels_mask)?; for chan_out in wave_out.iter_mut() { chan_out.truncate(out_len); } Ok(wave_out) } /// Resample a buffer of audio to a pre-allocated output buffer. /// Use this in real-time applications where the unpredictable time required to allocate /// memory from the heap can cause glitches. If this is not a problem, you may use /// the [process](Resampler::process) method instead. /// /// The input and output buffers are used in a non-interleaved format. /// The input is a slice, where each element of the slice is itself referenceable /// as a slice ([AsRef<\[T\]>](AsRef)) which contains the samples for a single channel. /// Because `[Vec]` implements [`AsRef<\[T\]>`](AsRef), the input may be [`Vec>`](Vec). /// /// The output data is a slice, where each element of the slice is a `[T]` which contains /// the samples for a single channel. If the output channel slices do not have sufficient /// capacity for all output samples, the function will return an error with the expected /// size. You could allocate the required output buffer with /// [output_buffer_allocate](Resampler::output_buffer_allocate) before calling this function /// and reuse the same buffer for each call. /// /// The `active_channels_mask` is optional. /// Any channel marked as inactive by a false value will be skipped during processing /// and the corresponding output will be left unchanged. /// If `None` is given, all channels will be considered active. /// /// Before processing, it checks that the input and outputs are valid. /// If either has the wrong number of channels, or if the buffer for any channel is too short, /// a [ResampleError] is returned. /// Both input and output are allowed to be longer than required. /// The number of input samples consumed and the number output samples written /// per channel is returned in a tuple, `(input_frames, output_frames)`. fn process_into_buffer, Vout: AsMut<[T]>>( &mut self, wave_in: &[Vin], wave_out: &mut [Vout], active_channels_mask: Option<&[bool]>, ) -> ResampleResult<(usize, usize)>; /// This is a convenience method for processing the last frames at the end of a stream. /// Use this when there are fewer frames remaining than what the resampler requires as input. /// Calling this function is equivalent to padding the input buffer with zeros /// to make it the right input length, and then calling [process_into_buffer](Resampler::process_into_buffer). /// This method can also be called without any input frames, by providing `None` as input buffer. /// This can be utilized to push any remaining delayed frames out from the internal buffers. /// Note that this method allocates space for a temporary input buffer. /// Real-time applications should instead call `process_into_buffer` with a zero-padded pre-allocated input buffer. fn process_partial_into_buffer, Vout: AsMut<[T]>>( &mut self, wave_in: Option<&[Vin]>, wave_out: &mut [Vout], active_channels_mask: Option<&[bool]>, ) -> ResampleResult<(usize, usize)> { let frames = self.input_frames_next(); let mut wave_in_padded = Vec::with_capacity(self.nbr_channels()); for _ in 0..self.nbr_channels() { wave_in_padded.push(vec![T::zero(); frames]); } if let Some(input) = wave_in { for (ch_input, ch_padded) in input.iter().zip(wave_in_padded.iter_mut()) { let mut frames_in = ch_input.as_ref().len(); if frames_in > frames { frames_in = frames; } if frames_in > 0 { ch_padded[..frames_in].copy_from_slice(&ch_input.as_ref()[..frames_in]); } else { ch_padded.clear(); } } } self.process_into_buffer(&wave_in_padded, wave_out, active_channels_mask) } /// This is a convenience method for processing the last frames at the end of a stream. /// It is similar to [process_partial_into_buffer](Resampler::process_partial_into_buffer) /// but allocates the output buffer with each call. /// Note that this method allocates space for both input and output. fn process_partial>( &mut self, wave_in: Option<&[V]>, active_channels_mask: Option<&[bool]>, ) -> ResampleResult>> { let frames = self.output_frames_next(); let channels = self.nbr_channels(); let mut wave_out = Vec::with_capacity(channels); for chan in 0..channels { let chan_out = if active_channels_mask.map(|mask| mask[chan]).unwrap_or(true) { vec![T::zero(); frames] } else { vec![] }; wave_out.push(chan_out); } let (_, out_len) = self.process_partial_into_buffer(wave_in, &mut wave_out, active_channels_mask)?; for chan_out in wave_out.iter_mut() { chan_out.truncate(out_len); } Ok(wave_out) } /// Convenience method for allocating an input buffer suitable for use with /// [process_into_buffer](Resampler::process_into_buffer). The buffer's capacity /// is big enough to prevent allocating additional heap memory before any call to /// [process_into_buffer](Resampler::process_into_buffer) regardless of the current /// resampling ratio. /// /// The `filled` argument determines if the vectors should be pre-filled with zeros or not. /// When false, the vectors are only allocated but returned empty. fn input_buffer_allocate(&self, filled: bool) -> Vec> { let frames = self.input_frames_max(); let channels = self.nbr_channels(); make_buffer(channels, frames, filled) } /// Get the maximum number of input frames per channel the resampler could require. fn input_frames_max(&self) -> usize; /// Get the number of frames per channel needed for the next call to /// [process_into_buffer](Resampler::process_into_buffer) or [process](Resampler::process). fn input_frames_next(&self) -> usize; /// Get the maximum number of channels this Resampler is configured for. fn nbr_channels(&self) -> usize; /// Convenience method for allocating an output buffer suitable for use with /// [process_into_buffer](Resampler::process_into_buffer). The buffer's capacity /// is big enough to prevent allocating additional heap memory during any call to /// [process_into_buffer](Resampler::process_into_buffer) regardless of the current /// resampling ratio. /// /// The `filled` argument determines if the vectors should be pre-filled with zeros or not. /// When false, the vectors are only allocated but returned empty. fn output_buffer_allocate(&self, filled: bool) -> Vec> { let frames = self.output_frames_max(); let channels = self.nbr_channels(); make_buffer(channels, frames, filled) } /// Get the max number of output frames per channel. fn output_frames_max(&self) -> usize; /// Get the number of frames per channel that will be output from the next call to /// [process_into_buffer](Resampler::process_into_buffer) or [process](Resampler::process). /// For the resamplers with a fixed output size, sush as [FastFixedOut], /// this gives the exact number. /// For the resamplers with a varying output size, like [FastFixedIn], /// the number is an estimation that may be a few frames larger than /// (and never smaller than) the actual number of output frames. fn output_frames_next(&self) -> usize; /// Get the delay for the resampler, reported as a number of output frames. fn output_delay(&self) -> usize; /// Update the resample ratio. /// /// For asynchronous resamplers, the ratio must be within /// `original / maximum` to `original * maximum`, where the original and maximum are the /// resampling ratios that were provided to the constructor. Trying to set the ratio /// outside these bounds will return [ResampleError::RatioOutOfBounds]. /// /// For synchronous resamplers, this will always return [ResampleError::SyncNotAdjustable]. /// /// If the argument `ramp` is set to true, the ratio will be ramped from the old to the new value /// during processing of the next chunk. This allows smooth transitions from one ratio to another. /// If `ramp` is false, the new ratio will be applied from the start of the next chunk. fn set_resample_ratio(&mut self, new_ratio: f64, ramp: bool) -> ResampleResult<()>; /// Update the resample ratio as a factor relative to the original one. /// /// For asynchronous resamplers, the relative ratio must be within /// `1 / maximum` to `maximum`, where `maximum` is the maximum /// resampling ratio that was provided to the constructor. Trying to set the ratio /// outside these bounds will return [ResampleError::RatioOutOfBounds]. /// /// Ratios above 1.0 slow down the output and lower the pitch, while ratios /// below 1.0 speed up the output and raise the pitch. /// /// For synchronous resamplers, this will always return [ResampleError::SyncNotAdjustable]. fn set_resample_ratio_relative(&mut self, rel_ratio: f64, ramp: bool) -> ResampleResult<()>; /// Reset the resampler state and clear all internal buffers. fn reset(&mut self); /// Change the chunk size for the resampler. /// This is not supported by all resampler types. /// The value must be equal to or smaller than the chunk size the value /// that the resampler was created with. /// [ResampleError::InvalidChunkSize] is returned if the value is zero or too large. /// /// The meaning of chunk size depends on the resampler, /// it refers to the input size for FixedIn, /// and output size for FixedOut types. /// /// Types that do not support changing the chunk size /// return [ResampleError::ChunkSizeNotAdjustable]. fn set_chunk_size(&mut self, _chunksize: usize) -> ResampleResult<()> { Err(ResampleError::ChunkSizeNotAdjustable) } } use crate as rubato; /// A macro for implementing wrapper traits for when a [Resampler] must be object safe. /// The wrapper trait locks the generic type parameters or the [Resampler] trait to specific types, /// which is needed to make the trait into an object. /// /// One wrapper trait, [VecResampler], is included per default. /// It differs from [Resampler] by fixing the generic types /// `&[AsRef<[T]>]` and `&mut [AsMut<[T]>]` to `&[Vec]` and `&mut [Vec]`. /// This allows a [VecResampler] to be made into a trait object like this: /// ``` /// # use rubato::{FastFixedIn, VecResampler, PolynomialDegree}; /// let boxed: Box> = Box::new(FastFixedIn::::new(44100 as f64 / 88200 as f64, 1.1, PolynomialDegree::Cubic, 2, 2).unwrap()); /// ``` /// Use this implementation as an example if you need to fix the input type to something else. #[macro_export] macro_rules! implement_resampler { ($trait_name:ident, $in_type:ty, $out_type:ty) => { #[doc = "This is an wrapper trait implemented via the [implement_resampler] macro."] #[doc = "The generic input and output types `&[AsRef<[T]>]` and `&mut [AsMut<[T]>]`"] #[doc = concat!("are locked to `", stringify!($in_type), "` and `", stringify!($out_type), "`.")] pub trait $trait_name: Send { /// Refer to [Resampler::process]. fn process( &mut self, wave_in: $in_type, active_channels_mask: Option<&[bool]>, ) -> rubato::ResampleResult>>; /// Refer to [Resampler::process_into_buffer]. fn process_into_buffer( &mut self, wave_in: $in_type, wave_out: $out_type, active_channels_mask: Option<&[bool]>, ) -> rubato::ResampleResult<(usize, usize)>; /// Refer to [Resampler::process_partial_into_buffer]. fn process_partial_into_buffer( &mut self, wave_in: Option<$in_type>, wave_out: $out_type, active_channels_mask: Option<&[bool]>, ) -> rubato::ResampleResult<(usize, usize)>; /// Refer to [Resampler::process_partial]. fn process_partial( &mut self, wave_in: Option<$in_type>, active_channels_mask: Option<&[bool]>, ) -> rubato::ResampleResult>>; /// Refer to [Resampler::input_buffer_allocate]. fn input_buffer_allocate(&self, filled: bool) -> Vec>; /// Refer to [Resampler::input_frames_max]. fn input_frames_max(&self) -> usize; /// Refer to [Resampler::input_frames_next]. fn input_frames_next(&self) -> usize; /// Refer to [Resampler::nbr_channels]. fn nbr_channels(&self) -> usize; /// Refer to [Resampler::output_buffer_allocate]. fn output_buffer_allocate(&self, filled: bool) -> Vec>; /// Refer to [Resampler::output_frames_max]. fn output_frames_max(&self) -> usize; /// Refer to [Resampler::output_frames_next]. fn output_frames_next(&self) -> usize; /// Refer to [Resampler::output_delay]. fn output_delay(&self) -> usize; /// Refer to [Resampler::set_resample_ratio]. fn set_resample_ratio(&mut self, new_ratio: f64, ramp: bool) -> rubato::ResampleResult<()>; /// Refer to [Resampler::set_resample_ratio_relative]. fn set_resample_ratio_relative(&mut self, rel_ratio: f64, ramp: bool) -> rubato::ResampleResult<()>; } impl $trait_name for U where U: rubato::Resampler, T: rubato::Sample, { fn process( &mut self, wave_in: $in_type, active_channels_mask: Option<&[bool]>, ) -> rubato::ResampleResult>> { rubato::Resampler::process(self, wave_in, active_channels_mask) } fn process_into_buffer( &mut self, wave_in: $in_type, wave_out: $out_type, active_channels_mask: Option<&[bool]>, ) -> rubato::ResampleResult<(usize, usize)> { rubato::Resampler::process_into_buffer(self, wave_in, wave_out, active_channels_mask) } fn process_partial_into_buffer( &mut self, wave_in: Option<$in_type>, wave_out: $out_type, active_channels_mask: Option<&[bool]>, ) -> rubato::ResampleResult<(usize, usize)> { rubato::Resampler::process_partial_into_buffer( self, wave_in.map(AsRef::as_ref), wave_out, active_channels_mask, ) } fn process_partial( &mut self, wave_in: Option<$in_type>, active_channels_mask: Option<&[bool]>, ) -> rubato::ResampleResult>> { rubato::Resampler::process_partial(self, wave_in, active_channels_mask) } fn output_buffer_allocate(&self, filled: bool) -> Vec> { rubato::Resampler::output_buffer_allocate(self, filled) } fn output_frames_next(&self) -> usize { rubato::Resampler::output_frames_next(self) } fn output_frames_max(&self) -> usize { rubato::Resampler::output_frames_max(self) } fn input_frames_next(&self) -> usize { rubato::Resampler::input_frames_next(self) } fn output_delay(&self) -> usize { rubato::Resampler::output_delay(self) } fn nbr_channels(&self) -> usize { rubato::Resampler::nbr_channels(self) } fn input_frames_max(&self) -> usize { rubato::Resampler::input_frames_max(self) } fn input_buffer_allocate(&self, filled: bool) -> Vec> { rubato::Resampler::input_buffer_allocate(self, filled) } fn set_resample_ratio(&mut self, new_ratio: f64, ramp: bool) -> rubato::ResampleResult<()> { rubato::Resampler::set_resample_ratio(self, new_ratio, ramp) } fn set_resample_ratio_relative(&mut self, rel_ratio: f64, ramp: bool) -> rubato::ResampleResult<()> { rubato::Resampler::set_resample_ratio_relative(self, rel_ratio, ramp) } } } } implement_resampler!(VecResampler, &[Vec], &mut [Vec]); /// Helper to make a mask where all channels are marked as active. fn update_mask_from_buffers(mask: &mut [bool]) { mask.iter_mut().for_each(|v| *v = true); } pub(crate) fn validate_buffers, Vout: AsMut<[T]>>( wave_in: &[Vin], wave_out: &mut [Vout], mask: &[bool], channels: usize, min_input_len: usize, min_output_len: usize, ) -> ResampleResult<()> { if wave_in.len() != channels { return Err(ResampleError::WrongNumberOfInputChannels { expected: channels, actual: wave_in.len(), }); } if mask.len() != channels { return Err(ResampleError::WrongNumberOfMaskChannels { expected: channels, actual: wave_in.len(), }); } for (chan, wave_in) in wave_in.iter().enumerate().filter(|(chan, _)| mask[*chan]) { let actual_len = wave_in.as_ref().len(); if actual_len < min_input_len { return Err(ResampleError::InsufficientInputBufferSize { channel: chan, expected: min_input_len, actual: actual_len, }); } } if wave_out.len() != channels { return Err(ResampleError::WrongNumberOfOutputChannels { expected: channels, actual: wave_out.len(), }); } for (chan, wave_out) in wave_out .iter_mut() .enumerate() .filter(|(chan, _)| mask[*chan]) { let actual_len = wave_out.as_mut().len(); if actual_len < min_output_len { return Err(ResampleError::InsufficientOutputBufferSize { channel: chan, expected: min_output_len, actual: actual_len, }); } } Ok(()) } /// Convenience method for allocating a buffer to hold a given number of channels and frames. /// The `filled` argument determines if the vectors should be pre-filled with zeros or not. /// When false, the vectors are only allocated but returned empty. pub fn make_buffer(channels: usize, frames: usize, filled: bool) -> Vec> { let mut buffer = Vec::with_capacity(channels); for _ in 0..channels { buffer.push(Vec::with_capacity(frames)); } if filled { resize_buffer(&mut buffer, frames) } buffer } /// Convenience method for resizing a buffer to a new number of frames. /// If the new number of frames is no larger than the buffer capacity, /// no reallocation will occur. /// If the new length is smaller than the current, the excess elements are dropped. /// If it is larger, zeros are inserted for the missing elements. pub fn resize_buffer(buffer: &mut [Vec], frames: usize) { buffer.iter_mut().for_each(|v| v.resize(frames, T::zero())); } /// Convenience method for getting the current length of a buffer in frames. /// Checks the [length](Vec::len) of the vector for each channel and returns the smallest. pub fn buffer_length(buffer: &[Vec]) -> usize { buffer.iter().map(|v| v.len()).min().unwrap_or_default() } /// Convenience method for getting the current allocated capacity of a buffer in frames. /// Checks the [capacity](Vec::capacity) of the vector for each channel and returns the smallest. pub fn buffer_capacity(buffer: &[Vec]) -> usize { buffer .iter() .map(|v| v.capacity()) .min() .unwrap_or_default() } #[cfg(test)] pub mod tests { use crate::{buffer_capacity, buffer_length, make_buffer, resize_buffer, VecResampler}; use crate::{FastFixedIn, PolynomialDegree, SincFixedIn, SincFixedOut}; #[cfg(feature = "fft_resampler")] use crate::{FftFixedIn, FftFixedInOut, FftFixedOut}; use test_log::test; // This tests that a VecResampler can be boxed. #[test] fn boxed_resampler() { let mut boxed: Box> = Box::new( FastFixedIn::::new( 88200 as f64 / 44100 as f64, 1.1, PolynomialDegree::Cubic, 1024, 2, ) .unwrap(), ); let _ = process_with_boxed(&mut boxed); let result = process_with_boxed(&mut boxed); assert_eq!(result.len(), 2); assert_eq!(result[0].len(), 2048); assert_eq!(result[1].len(), 2048); } fn process_with_boxed(resampler: &mut Box>) -> Vec> { let frames = resampler.input_frames_next(); let waves = vec![vec![0.0f64; frames]; 2]; resampler.process(&waves, None).unwrap() } fn impl_send() { fn is_send() {} is_send::>(); is_send::>(); #[cfg(feature = "fft_resampler")] { is_send::>(); is_send::>(); is_send::>(); } } // This tests that all resamplers are Send. #[test] fn test_impl_send() { impl_send::(); impl_send::(); } #[macro_export] macro_rules! check_output { ($resampler:ident) => { let mut val = 0.0; let mut prev_last = -0.1; let max_input_len = $resampler.input_frames_max(); let max_output_len = $resampler.output_frames_max(); for n in 0..50 { let frames = $resampler.input_frames_next(); // Check that lengths are within the reported max values assert!( frames <= max_input_len, "Iteration {}, input frames {} larger than max {}", n, frames, max_input_len ); let out_frames = $resampler.output_frames_next(); assert!( out_frames <= max_output_len, "Iteration {}, output frames {} larger than max {}", n, out_frames, max_output_len ); let mut waves = vec![vec![0.0f64; frames]; 2]; for m in 0..frames { for ch in 0..2 { waves[ch][m] = val; } val = val + 0.1; } let out = $resampler.process(&waves, None).unwrap(); let frames_out = out[0].len(); for ch in 0..2 { assert!( out[ch][0] > prev_last, "Iteration {}, first value {} prev last value {}", n, out[ch][0], prev_last ); let expected_diff = frames as f64 * 0.1; let diff = out[ch][frames_out - 1] - out[ch][0]; assert!( diff < 1.5 * expected_diff && diff > 0.25 * expected_diff, "Iteration {}, last value {} first value {}", n, out[ch][frames_out - 1], out[ch][0] ); } prev_last = out[0][frames_out - 1]; for m in 0..frames_out - 1 { for ch in 0..2 { let diff = out[ch][m + 1] - out[ch][m]; assert!( diff < 0.15 && diff > -0.05, "Frame {}:{} next value {} value {}", n, m, out[ch][m + 1], out[ch][m] ); } } } }; } #[macro_export] macro_rules! check_ratio { ($resampler:ident, $ratio:ident, $repetitions:literal) => { let input = $resampler.input_buffer_allocate(true); let mut output = $resampler.output_buffer_allocate(true); let mut total_in = 0; let mut total_out = 0; for _ in 0..$repetitions { let out = $resampler .process_into_buffer(&input, &mut output, None) .unwrap(); total_in += out.0; total_out += out.1 } let measured_ratio = total_out as f64 / total_in as f64; assert!(measured_ratio > 0.999 * $ratio); assert!(measured_ratio < 1.001 * $ratio); }; } #[test] fn test_buffer_helpers() { let buf1 = vec![vec![0.0f64; 7], vec![0.0f64; 5], vec![0.0f64; 10]]; assert_eq!(buffer_length(&buf1), 5); let mut buf2 = vec![Vec::::with_capacity(5), Vec::::with_capacity(15)]; assert_eq!(buffer_length(&buf2), 0); assert_eq!(buffer_capacity(&buf2), 5); resize_buffer(&mut buf2, 3); assert_eq!(buffer_length(&buf2), 3); assert_eq!(buffer_capacity(&buf2), 5); let buf3 = make_buffer::(4, 10, false); assert_eq!(buffer_length(&buf3), 0); assert_eq!(buffer_capacity(&buf3), 10); let buf4 = make_buffer::(4, 10, true); assert_eq!(buffer_length(&buf4), 10); assert_eq!(buffer_capacity(&buf4), 10); } } rubato-0.16.2/src/sample.rs000064400000000000000000000055101046102023000136250ustar 00000000000000use crate::sinc_interpolator::{AvxSample, NeonSample, SseSample}; #[cfg(feature = "fft_resampler")] use realfft::FftNum; #[cfg(not(feature = "fft_resampler"))] use num_traits::{FromPrimitive, Signed}; #[cfg(not(feature = "fft_resampler"))] use std::fmt::Debug; #[cfg(not(feature = "fft_resampler"))] pub trait FftNum: Copy + FromPrimitive + Signed + Sync + Send + Debug + 'static {} #[cfg(not(feature = "fft_resampler"))] impl FftNum for T where T: Copy + FromPrimitive + Signed + Sync + Send + Debug + 'static {} /// The trait governing a single sample. /// /// There are two types which implements this trait so far: /// * [f32] /// * [f64] pub trait Sample where Self: Copy + CoerceFrom + CoerceFrom + CoerceFrom + FftNum + std::ops::Mul + std::ops::Div + std::ops::Add + std::ops::Sub + std::ops::MulAssign + std::ops::RemAssign + std::ops::DivAssign + std::ops::SubAssign + std::ops::AddAssign + AvxSample + SseSample + NeonSample + Send, { const PI: Self; /// Calculate the sine of `self`. fn sin(self) -> Self; /// Calculate the cosine of `self`. fn cos(self) -> Self; /// Coerce `value` into the current type. /// /// Coercions are governed through the private `CoerceFrom` trait. fn coerce(value: T) -> Self where Self: CoerceFrom, { Self::coerce_from(value) } } impl Sample for f32 { const PI: Self = std::f32::consts::PI; fn sin(self) -> Self { f32::sin(self) } fn cos(self) -> Self { f32::cos(self) } } impl Sample for f64 { const PI: Self = std::f64::consts::PI; fn sin(self) -> Self { f64::sin(self) } fn cos(self) -> Self { f64::cos(self) } } /// The trait used to coerce a value infallibly from one type to another. /// /// This is similar to doing `value as T` where `T` is a floating point type. /// Loss of precision may happen during coercions if the coerced from value /// doesn't fit fully within the target type. pub trait CoerceFrom { /// Perform a coercion from `value` into the current type. fn coerce_from(value: T) -> Self; } impl CoerceFrom for f32 { fn coerce_from(value: usize) -> Self { value as f32 } } impl CoerceFrom for f64 { fn coerce_from(value: usize) -> Self { value as f64 } } impl CoerceFrom for f32 { fn coerce_from(value: f64) -> Self { value as f32 } } impl CoerceFrom for f64 { fn coerce_from(value: f64) -> Self { value } } impl CoerceFrom for f32 { fn coerce_from(value: f32) -> Self { value } } impl CoerceFrom for f64 { fn coerce_from(value: f32) -> Self { value as f64 } } rubato-0.16.2/src/sinc.rs000064400000000000000000000032331046102023000133000ustar 00000000000000use crate::windows::{make_window, WindowFunction}; use crate::Sample; /// Helper function: sinc(x) = sin(pi*x)/(pi*x). pub fn sinc(value: T) -> T where T: Sample, { if value == T::zero() { T::one() } else { (value * T::PI).sin() / (value * T::PI) } } /// Helper function. Make a set of windowed sincs. pub fn make_sincs( npoints: usize, factor: usize, f_cutoff: f32, windowfunc: WindowFunction, ) -> Vec> where T: Sample, { let totpoints = npoints * factor; let mut y = Vec::with_capacity(totpoints); let window = make_window::(totpoints, windowfunc); let mut sum = T::zero(); for (x, w) in window.iter().enumerate().take(totpoints) { let val = *w * sinc( (T::coerce(x) - T::coerce(totpoints / 2)) * T::coerce(f_cutoff) / T::coerce(factor), ); sum += val; y.push(val); } sum /= T::coerce(factor); debug!( "Generate sincs, length: {}, oversampling: {}, normalized by: {:?}", npoints, factor, sum ); let mut sincs = vec![vec![T::zero(); npoints]; factor]; for p in 0..npoints { for n in 0..factor { sincs[factor - n - 1][p] = y[factor * p + n] / sum; } } sincs } #[cfg(test)] mod tests { use crate::sinc::make_sincs; use crate::WindowFunction; use test_log::test; #[test] fn sincs() { let sincs = make_sincs::(32, 8, 0.9, WindowFunction::Blackman); assert!((sincs[7][16] - 1.0).abs() < 0.2); let sum: f64 = sincs.iter().map(|v| v.iter().sum::()).sum(); assert!((sum - 8.0).abs() < 0.00001); } } rubato-0.16.2/src/sinc_interpolator/mod.rs000064400000000000000000000144551046102023000166710ustar 00000000000000use crate::sinc::make_sincs; use crate::windows::WindowFunction; use crate::Sample; /// Helper macro to define a dummy implementation of the sample trait if a /// feature is not supported. macro_rules! interpolator { ( #[cfg($($cond:tt)*)] mod $mod:ident; trait $trait:ident; ) => { #[cfg($($cond)*)] pub mod $mod; #[cfg(not($($cond)*))] pub mod $mod { use crate::Sample; /// Dummy trait when not supported. pub trait $trait { } /// Dummy impl of trait when not supported. impl $trait for T where T: Sample { } } pub use self::$mod::$trait; } } interpolator! { #[cfg(target_arch = "x86_64")] mod sinc_interpolator_avx; trait AvxSample; } interpolator! { #[cfg(target_arch = "x86_64")] mod sinc_interpolator_sse; trait SseSample; } interpolator! { #[cfg(target_arch = "aarch64")] mod sinc_interpolator_neon; trait NeonSample; } /// Functions for making the scalar product with a sinc. pub trait SincInterpolator: Send { /// Make the scalar product between the waveform starting at `index` and the sinc of `subindex`. fn get_sinc_interpolated(&self, wave: &[T], index: usize, subindex: usize) -> T; /// Get sinc length. fn len(&self) -> usize; /// Check if sincs are empty. fn is_empty(&self) -> bool { self.len() == 0 } /// Get number of sincs used for oversampling. fn nbr_sincs(&self) -> usize; } /// A plain scalar interpolator. pub struct ScalarInterpolator { sincs: Vec>, length: usize, nbr_sincs: usize, } impl SincInterpolator for ScalarInterpolator where T: Sample, { /// Calculate the scalar produt of an input wave and the selected sinc filter. fn get_sinc_interpolated(&self, wave: &[T], index: usize, subindex: usize) -> T { assert!( (index + self.length) < wave.len(), "Tried to interpolate for index {}, max for the given input is {}", index, wave.len() - self.length - 1 ); assert!( subindex < self.nbr_sincs, "Tried to use sinc subindex {}, max is {}", subindex, self.nbr_sincs - 1 ); let wave_cut = &wave[index..(index + self.sincs[subindex].len())]; let sinc = &self.sincs[subindex]; unsafe { let mut acc0 = T::zero(); let mut acc1 = T::zero(); let mut acc2 = T::zero(); let mut acc3 = T::zero(); let mut acc4 = T::zero(); let mut acc5 = T::zero(); let mut acc6 = T::zero(); let mut acc7 = T::zero(); let mut idx = 0; for _ in 0..wave_cut.len() / 8 { acc0 += *wave_cut.get_unchecked(idx) * *sinc.get_unchecked(idx); acc1 += *wave_cut.get_unchecked(idx + 1) * *sinc.get_unchecked(idx + 1); acc2 += *wave_cut.get_unchecked(idx + 2) * *sinc.get_unchecked(idx + 2); acc3 += *wave_cut.get_unchecked(idx + 3) * *sinc.get_unchecked(idx + 3); acc4 += *wave_cut.get_unchecked(idx + 4) * *sinc.get_unchecked(idx + 4); acc5 += *wave_cut.get_unchecked(idx + 5) * *sinc.get_unchecked(idx + 5); acc6 += *wave_cut.get_unchecked(idx + 6) * *sinc.get_unchecked(idx + 6); acc7 += *wave_cut.get_unchecked(idx + 7) * *sinc.get_unchecked(idx + 7); idx += 8; } acc0 + acc1 + acc2 + acc3 + acc4 + acc5 + acc6 + acc7 } } fn len(&self) -> usize { self.length } fn nbr_sincs(&self) -> usize { self.nbr_sincs } } impl ScalarInterpolator where T: Sample, { /// Create a new ScalarInterpolator. /// /// Parameters are: /// - `sinc_len`: Length of sinc functions. /// - `oversampling_factor`: Number of intermediate sincs (oversampling factor). /// - `f_cutoff`: Relative cutoff frequency. /// - `window`: Window function to use. pub fn new( sinc_len: usize, oversampling_factor: usize, f_cutoff: f32, window: WindowFunction, ) -> Self { assert!(sinc_len % 8 == 0, "Sinc length must be a multiple of 8"); let sincs = make_sincs(sinc_len, oversampling_factor, f_cutoff, window); Self { sincs, length: sinc_len, nbr_sincs: oversampling_factor, } } } #[cfg(test)] mod tests { use super::ScalarInterpolator; use super::SincInterpolator; use crate::WindowFunction; use num_traits::Float; use rand::Rng; use test_log::test; fn get_sinc_interpolated(wave: &[T], index: usize, sinc: &[T]) -> T { let wave_cut = &wave[index..(index + sinc.len())]; wave_cut .iter() .zip(sinc.iter()) .fold(T::zero(), |acc, (x, y)| acc + *x * *y) } #[test] fn test_scalar_interpolator_64() { let mut rng = rand::thread_rng(); let mut wave = Vec::new(); for _ in 0..2048 { wave.push(rng.gen::()); } let sinc_len = 256; let f_cutoff = 0.9473371669037001; let oversampling_factor = 256; let window = WindowFunction::BlackmanHarris2; let interpolator = ScalarInterpolator::::new(sinc_len, oversampling_factor, f_cutoff, window); let value = interpolator.get_sinc_interpolated(&wave, 333, 123); let check = get_sinc_interpolated(&wave, 333, &interpolator.sincs[123]); assert!((value - check).abs() < 1.0e-9); } #[test] fn test_scalar_interpolator_32() { let mut rng = rand::thread_rng(); let mut wave = Vec::new(); for _ in 0..2048 { wave.push(rng.gen::()); } let sinc_len = 256; let f_cutoff = 0.9473371669037001; let oversampling_factor = 256; let window = WindowFunction::BlackmanHarris2; let interpolator = ScalarInterpolator::::new(sinc_len, oversampling_factor, f_cutoff, window); let value = interpolator.get_sinc_interpolated(&wave, 333, 123); let check = get_sinc_interpolated(&wave, 333, &interpolator.sincs[123]); assert!((value - check).abs() < 1.0e-6); } } rubato-0.16.2/src/sinc_interpolator/sinc_interpolator_avx.rs000064400000000000000000000222351046102023000225210ustar 00000000000000use crate::error::{CpuFeature, MissingCpuFeature}; use crate::sinc::make_sincs; use crate::sinc_interpolator::SincInterpolator; use crate::windows::WindowFunction; use crate::Sample; use core::arch::x86_64::{ __m256, __m256d, _mm256_castpd256_pd128, _mm256_castps256_ps128, _mm256_extractf128_pd, _mm256_extractf128_ps, }; use core::arch::x86_64::{ _mm256_add_pd, _mm256_fmadd_pd, _mm256_loadu_pd, _mm256_setzero_pd, _mm_add_pd, _mm_hadd_pd, _mm_store_sd, }; use core::arch::x86_64::{ _mm256_fmadd_ps, _mm256_loadu_ps, _mm256_setzero_ps, _mm_add_ps, _mm_hadd_ps, _mm_store_ss, }; /// Collection of cpu features required for this interpolator. static FEATURES: &[CpuFeature] = &[CpuFeature::Avx, CpuFeature::Fma]; /// Trait governing what can be done with an AvxSample. pub trait AvxSample: Sized + Send { type Sinc: Send; /// Pack sincs into a vector. /// /// # Safety /// /// This is unsafe because it uses target_enable dispatching. There are no /// special requirements from the caller. unsafe fn pack_sincs(sincs: Vec>) -> Vec>; /// Interpolate a sinc sample. /// /// # Safety /// /// The caller must ensure that the various indexes are not out of bounds /// in the collection of sincs. unsafe fn get_sinc_interpolated_unsafe( wave: &[Self], index: usize, subindex: usize, sincs: &[Vec], length: usize, ) -> Self; } impl AvxSample for f32 { type Sinc = __m256; #[target_feature(enable = "avx", enable = "fma")] unsafe fn pack_sincs(sincs: Vec>) -> Vec> { let mut packed_sincs = Vec::new(); for sinc in sincs.iter() { let mut packed = Vec::new(); for elements in sinc.chunks(8) { let packed_elems = _mm256_loadu_ps(&elements[0]); packed.push(packed_elems); } packed_sincs.push(packed); } packed_sincs } #[target_feature(enable = "avx", enable = "fma")] unsafe fn get_sinc_interpolated_unsafe( wave: &[f32], index: usize, subindex: usize, sincs: &[Vec], length: usize, ) -> f32 { let sinc = sincs.get_unchecked(subindex); let wave_cut = &wave[index..(index + length)]; let mut acc = _mm256_setzero_ps(); let mut w_idx = 0; for s_idx in 0..length / 8 { let w = _mm256_loadu_ps(wave_cut.get_unchecked(w_idx)); acc = _mm256_fmadd_ps(w, *sinc.get_unchecked(s_idx), acc); w_idx += 8; } let acc_high = _mm256_extractf128_ps(acc, 1); let acc_low = _mm_add_ps(acc_high, _mm256_castps256_ps128(acc)); let temp2 = _mm_hadd_ps(acc_low, acc_low); let temp1 = _mm_hadd_ps(temp2, temp2); let mut result = 0.0; _mm_store_ss(&mut result, temp1); result } } impl AvxSample for f64 { type Sinc = __m256d; #[target_feature(enable = "avx", enable = "fma")] unsafe fn pack_sincs(sincs: Vec>) -> Vec> { let mut packed_sincs = Vec::new(); for sinc in sincs.iter() { let mut packed = Vec::new(); for elements in sinc.chunks(4) { let packed_elems = _mm256_loadu_pd(&elements[0]); packed.push(packed_elems); } packed_sincs.push(packed); } packed_sincs } #[target_feature(enable = "avx", enable = "fma")] unsafe fn get_sinc_interpolated_unsafe( wave: &[f64], index: usize, subindex: usize, sincs: &[Vec], length: usize, ) -> f64 { let sinc = sincs.get_unchecked(subindex); let wave_cut = &wave[index..(index + length)]; let mut acc0 = _mm256_setzero_pd(); let mut acc1 = _mm256_setzero_pd(); let mut w_idx = 0; let mut s_idx = 0; for _ in 0..wave_cut.len() / 8 { let w0 = _mm256_loadu_pd(wave_cut.get_unchecked(w_idx)); let w1 = _mm256_loadu_pd(wave_cut.get_unchecked(w_idx + 4)); acc0 = _mm256_fmadd_pd(w0, *sinc.get_unchecked(s_idx), acc0); acc1 = _mm256_fmadd_pd(w1, *sinc.get_unchecked(s_idx + 1), acc1); w_idx += 8; s_idx += 2; } let acc_all = _mm256_add_pd(acc0, acc1); let acc_high = _mm256_extractf128_pd(acc_all, 1); let temp2 = _mm_add_pd(acc_high, _mm256_castpd256_pd128(acc_all)); let temp1 = _mm_hadd_pd(temp2, temp2); let mut result = 0.0; _mm_store_sd(&mut result, temp1); result } } /// An AVX accelerated interpolator. pub struct AvxInterpolator where T: AvxSample, { sincs: Vec>, length: usize, nbr_sincs: usize, } impl SincInterpolator for AvxInterpolator where T: AvxSample, { /// Calculate the scalar produt of an input wave and the selected sinc filter. fn get_sinc_interpolated(&self, wave: &[T], index: usize, subindex: usize) -> T { assert!( (index + self.length) < wave.len(), "Tried to interpolate for index {}, max for the given input is {}", index, wave.len() - self.length - 1 ); assert!( subindex < self.nbr_sincs, "Tried to use sinc subindex {}, max is {}", subindex, self.nbr_sincs - 1 ); unsafe { T::get_sinc_interpolated_unsafe(wave, index, subindex, &self.sincs, self.length) } } fn len(&self) -> usize { self.length } fn nbr_sincs(&self) -> usize { self.nbr_sincs } } impl AvxInterpolator where T: Sample, { /// Create a new AvxInterpolator. /// /// Parameters are: /// - `sinc_len`: Length of sinc functions. /// - `oversampling_factor`: Number of intermediate sincs (oversampling factor). /// - `f_cutoff`: Relative cutoff frequency. /// - `window`: Window function to use. pub fn new( sinc_len: usize, oversampling_factor: usize, f_cutoff: f32, window: WindowFunction, ) -> Result { if let Some(feature) = FEATURES.iter().find(|f| !f.is_detected()) { return Err(MissingCpuFeature(*feature)); } assert!(sinc_len % 8 == 0, "Sinc length must be a multiple of 8."); let sincs = make_sincs(sinc_len, oversampling_factor, f_cutoff, window); let sincs = unsafe { ::pack_sincs(sincs) }; Ok(Self { sincs, length: sinc_len, nbr_sincs: oversampling_factor, }) } } #[cfg(test)] mod tests { use crate::sinc::make_sincs; use crate::sinc_interpolator::sinc_interpolator_avx::AvxInterpolator; use crate::sinc_interpolator::SincInterpolator; use crate::WindowFunction; use num_traits::Float; use rand::Rng; use test_log::test; fn get_sinc_interpolated(wave: &[T], index: usize, sinc: &[T]) -> T { let wave_cut = &wave[index..(index + sinc.len())]; wave_cut .iter() .zip(sinc.iter()) .fold(T::zero(), |acc, (x, y)| acc + *x * *y) } #[test] fn test_avx_interpolator_64() { let mut rng = rand::thread_rng(); let mut wave = Vec::new(); for _ in 0..2048 { wave.push(rng.gen::()); } let sinc_len = 256; let f_cutoff = 0.9473371669037001; let oversampling_factor = 256; let window = WindowFunction::BlackmanHarris2; let sincs = make_sincs::(sinc_len, oversampling_factor, f_cutoff, window); let interpolator = match AvxInterpolator::::new(sinc_len, oversampling_factor, f_cutoff, window) { Ok(interpolator) => interpolator, Err(..) => { assert!(!(is_x86_feature_detected!("avx") && is_x86_feature_detected!("fma"))); return; } }; let value = interpolator.get_sinc_interpolated(&wave, 333, 123); let check = get_sinc_interpolated(&wave, 333, &sincs[123]); assert!((value - check).abs() < 1.0e-9); } #[test] fn test_avx_interpolator_32() { let mut rng = rand::thread_rng(); let mut wave = Vec::new(); for _ in 0..2048 { wave.push(rng.gen::()); } let sinc_len = 256; let f_cutoff = 0.9473371669037001; let oversampling_factor = 256; let window = WindowFunction::BlackmanHarris2; let sincs = make_sincs::(sinc_len, oversampling_factor, f_cutoff, window); let interpolator = match AvxInterpolator::::new(sinc_len, oversampling_factor, f_cutoff, window) { Ok(interpolator) => interpolator, Err(..) => { assert!(!(is_x86_feature_detected!("avx") && is_x86_feature_detected!("fma"))); return; } }; let value = interpolator.get_sinc_interpolated(&wave, 333, 123); let check = get_sinc_interpolated(&wave, 333, &sincs[123]); assert!((value - check).abs() < 1.0e-5); } } rubato-0.16.2/src/sinc_interpolator/sinc_interpolator_neon.rs000064400000000000000000000216711046102023000226650ustar 00000000000000use crate::error::{CpuFeature, MissingCpuFeature}; use crate::sinc::make_sincs; use crate::sinc_interpolator::SincInterpolator; use crate::windows::WindowFunction; use crate::Sample; use core::arch::aarch64::{float32x4_t, float64x2_t}; use core::arch::aarch64::{ vadd_f32, vaddq_f32, vfmaq_f32, vget_high_f32, vget_low_f32, vld1q_f32, vmovq_n_f32, vst1_f32, }; use core::arch::aarch64::{vaddq_f64, vfmaq_f64, vld1q_f64, vmovq_n_f64, vst1q_f64}; /// Collection of cpu features required for this interpolator. static FEATURES: &[CpuFeature] = &[CpuFeature::Neon]; /// Trait governing what can be done with an NeonSample. pub trait NeonSample: Sized + Send { type Sinc: Send; /// Pack sincs into a vector. /// /// # Safety /// /// This is unsafe because it uses target_enable dispatching. There are no /// special requirements from the caller. unsafe fn pack_sincs(sincs: Vec>) -> Vec>; /// Interpolate a sinc sample. /// /// # Safety /// /// The caller must ensure that the various indexes are not out of bounds /// in the collection of sincs. unsafe fn get_sinc_interpolated_unsafe( wave: &[Self], index: usize, subindex: usize, sincs: &[Vec], length: usize, ) -> Self; } impl NeonSample for f32 { type Sinc = float32x4_t; #[target_feature(enable = "neon")] unsafe fn pack_sincs(sincs: Vec>) -> Vec> { let mut packed_sincs = Vec::new(); for sinc in sincs.iter() { let mut packed = Vec::new(); for elements in sinc.chunks(4) { let packed_elems = vld1q_f32(&elements[0]); packed.push(packed_elems); } packed_sincs.push(packed); } packed_sincs } #[target_feature(enable = "neon")] unsafe fn get_sinc_interpolated_unsafe( wave: &[f32], index: usize, subindex: usize, sincs: &[Vec], length: usize, ) -> f32 { let sinc = sincs.get_unchecked(subindex); let wave_cut = &wave[index..(index + length)]; let mut acc0 = vmovq_n_f32(0.0); let mut acc1 = vmovq_n_f32(0.0); let mut w_idx = 0; let mut s_idx = 0; for _ in 0..wave_cut.len() / 8 { let w0 = vld1q_f32(wave_cut.get_unchecked(w_idx)); let w1 = vld1q_f32(wave_cut.get_unchecked(w_idx + 4)); acc0 = vfmaq_f32(acc0, w0, *sinc.get_unchecked(s_idx)); acc1 = vfmaq_f32(acc1, w1, *sinc.get_unchecked(s_idx + 1)); w_idx += 8; s_idx += 2; } let sum4 = vaddq_f32(acc0, acc1); let high = vget_high_f32(sum4); let low = vget_low_f32(sum4); let sum2 = vadd_f32(high, low); let mut array = [0.0, 0.0]; vst1_f32(array.as_mut_ptr(), sum2); array[0] + array[1] } } impl NeonSample for f64 { type Sinc = float64x2_t; #[target_feature(enable = "neon")] unsafe fn pack_sincs(sincs: Vec>) -> Vec> { let mut packed_sincs = Vec::new(); for sinc in sincs.iter() { let mut packed = Vec::new(); for elements in sinc.chunks(2) { let packed_elems = vld1q_f64(&elements[0]); packed.push(packed_elems); } packed_sincs.push(packed); } packed_sincs } #[target_feature(enable = "neon")] unsafe fn get_sinc_interpolated_unsafe( wave: &[f64], index: usize, subindex: usize, sincs: &[Vec], length: usize, ) -> f64 { let sinc = sincs.get_unchecked(subindex); let wave_cut = &wave[index..(index + length)]; let mut acc0 = vmovq_n_f64(0.0); let mut acc1 = vmovq_n_f64(0.0); let mut acc2 = vmovq_n_f64(0.0); let mut acc3 = vmovq_n_f64(0.0); let mut w_idx = 0; let mut s_idx = 0; for _ in 0..wave_cut.len() / 8 { let w0 = vld1q_f64(wave_cut.get_unchecked(w_idx)); let w1 = vld1q_f64(wave_cut.get_unchecked(w_idx + 2)); let w2 = vld1q_f64(wave_cut.get_unchecked(w_idx + 4)); let w3 = vld1q_f64(wave_cut.get_unchecked(w_idx + 6)); acc0 = vfmaq_f64(acc0, w0, *sinc.get_unchecked(s_idx)); acc1 = vfmaq_f64(acc1, w1, *sinc.get_unchecked(s_idx + 1)); acc2 = vfmaq_f64(acc2, w2, *sinc.get_unchecked(s_idx + 2)); acc3 = vfmaq_f64(acc3, w3, *sinc.get_unchecked(s_idx + 3)); w_idx += 8; s_idx += 4; } let packedsum0 = vaddq_f64(acc0, acc1); let packedsum1 = vaddq_f64(acc2, acc3); let packedsum2 = vaddq_f64(packedsum0, packedsum1); let mut values = [0.0, 0.0]; vst1q_f64(values.as_mut_ptr(), packedsum2); values[0] + values[1] } } /// A SSE accelerated interpolator. pub struct NeonInterpolator where T: NeonSample, { sincs: Vec>, length: usize, nbr_sincs: usize, } impl SincInterpolator for NeonInterpolator where T: Sample, { /// Calculate the scalar produt of an input wave and the selected sinc filter. fn get_sinc_interpolated(&self, wave: &[T], index: usize, subindex: usize) -> T { assert!( (index + self.length) < wave.len(), "Tried to interpolate for index {}, max for the given input is {}", index, wave.len() - self.length - 1 ); assert!( subindex < self.nbr_sincs, "Tried to use sinc subindex {}, max is {}", subindex, self.nbr_sincs - 1 ); unsafe { T::get_sinc_interpolated_unsafe(wave, index, subindex, &self.sincs, self.length) } } fn len(&self) -> usize { self.length } fn nbr_sincs(&self) -> usize { self.nbr_sincs } } impl NeonInterpolator where T: Sample, { /// Create a new NeonInterpolator. /// /// Parameters are: /// - `sinc_len`: Length of sinc functions. /// - `oversampling_factor`: Number of intermediate sincs (oversampling factor). /// - `f_cutoff`: Relative cutoff frequency. /// - `window`: Window function to use. pub fn new( sinc_len: usize, oversampling_factor: usize, f_cutoff: f32, window: WindowFunction, ) -> Result { if let Some(feature) = FEATURES.iter().find(|f| !f.is_detected()) { return Err(MissingCpuFeature(*feature)); } assert!(sinc_len % 8 == 0, "Sinc length must be a multiple of 8."); let sincs = make_sincs(sinc_len, oversampling_factor, f_cutoff, window); let sincs = unsafe { ::pack_sincs(sincs) }; Ok(Self { sincs, length: sinc_len, nbr_sincs: oversampling_factor, }) } } #[cfg(test)] mod tests { use crate::sinc::make_sincs; use crate::sinc_interpolator::sinc_interpolator_neon::NeonInterpolator; use crate::sinc_interpolator::SincInterpolator; use crate::WindowFunction; use num_traits::Float; use rand::Rng; use test_log::test; fn get_sinc_interpolated(wave: &[T], index: usize, sinc: &[T]) -> T { let wave_cut = &wave[index..(index + sinc.len())]; wave_cut .iter() .zip(sinc.iter()) .fold(T::zero(), |acc, (x, y)| acc + *x * *y) } #[test] fn test_neon_interpolator_64() { let mut rng = rand::thread_rng(); let mut wave = Vec::new(); for _ in 0..2048 { wave.push(rng.gen::()); } let sinc_len = 256; let f_cutoff = 0.9473371669037001; let oversampling_factor = 256; let window = WindowFunction::BlackmanHarris2; let sincs = make_sincs::(sinc_len, oversampling_factor, f_cutoff, window); let interpolator = NeonInterpolator::::new(sinc_len, oversampling_factor, f_cutoff, window).unwrap(); let value = interpolator.get_sinc_interpolated(&wave, 333, 123); let check = get_sinc_interpolated(&wave, 333, &sincs[123]); assert!((value - check).abs() < 1.0e-9); } #[test] fn test_neon_interpolator_32() { let mut rng = rand::thread_rng(); let mut wave = Vec::new(); for _ in 0..2048 { wave.push(rng.gen::()); } let sinc_len = 256; let f_cutoff = 0.9473371669037001; let oversampling_factor = 256; let window = WindowFunction::BlackmanHarris2; let sincs = make_sincs::(sinc_len, oversampling_factor, f_cutoff, window); let interpolator = NeonInterpolator::::new(sinc_len, oversampling_factor, f_cutoff, window).unwrap(); let value = interpolator.get_sinc_interpolated(&wave, 333, 123); let check = get_sinc_interpolated(&wave, 333, &sincs[123]); assert!((value - check).abs() < 1.0e-5); } } rubato-0.16.2/src/sinc_interpolator/sinc_interpolator_sse.rs000064400000000000000000000221731046102023000225160ustar 00000000000000use crate::error::{CpuFeature, MissingCpuFeature}; use crate::sinc::make_sincs; use crate::sinc_interpolator::SincInterpolator; use crate::windows::WindowFunction; use crate::Sample; use core::arch::x86_64::{__m128, __m128d}; use core::arch::x86_64::{ _mm_add_pd, _mm_hadd_pd, _mm_loadu_pd, _mm_mul_pd, _mm_setzero_pd, _mm_store_sd, }; use core::arch::x86_64::{ _mm_add_ps, _mm_hadd_ps, _mm_loadu_ps, _mm_mul_ps, _mm_setzero_ps, _mm_store_ss, }; /// Collection of cpu features required for this interpolator. static FEATURES: &[CpuFeature] = &[CpuFeature::Sse3]; /// Trait governing what can be done with an SseSample. pub trait SseSample: Sized + Send { type Sinc: Send; /// Pack sincs into a vector. /// /// # Safety /// /// This is unsafe because it uses target_enable dispatching. There are no /// special requirements from the caller. unsafe fn pack_sincs(sincs: Vec>) -> Vec>; /// Interpolate a sinc sample. /// /// # Safety /// /// The caller must ensure that the various indexes are not out of bounds /// in the collection of sincs. unsafe fn get_sinc_interpolated_unsafe( wave: &[Self], index: usize, subindex: usize, sincs: &[Vec], length: usize, ) -> Self; } impl SseSample for f32 { type Sinc = __m128; #[target_feature(enable = "sse3")] unsafe fn pack_sincs(sincs: Vec>) -> Vec> { let mut packed_sincs = Vec::new(); for sinc in sincs.iter() { let mut packed = Vec::new(); for elements in sinc.chunks(4) { let packed_elems = _mm_loadu_ps(&elements[0]); packed.push(packed_elems); } packed_sincs.push(packed); } packed_sincs } #[target_feature(enable = "sse3")] unsafe fn get_sinc_interpolated_unsafe( wave: &[f32], index: usize, subindex: usize, sincs: &[Vec], length: usize, ) -> f32 { let sinc = sincs.get_unchecked(subindex); let wave_cut = &wave[index..(index + length)]; let mut acc0 = _mm_setzero_ps(); let mut acc1 = _mm_setzero_ps(); let mut w_idx = 0; let mut s_idx = 0; for _ in 0..wave_cut.len() / 8 { let w0 = _mm_loadu_ps(wave_cut.get_unchecked(w_idx)); let w1 = _mm_loadu_ps(wave_cut.get_unchecked(w_idx + 4)); let s0 = _mm_mul_ps(w0, *sinc.get_unchecked(s_idx)); let s1 = _mm_mul_ps(w1, *sinc.get_unchecked(s_idx + 1)); acc0 = _mm_add_ps(acc0, s0); acc1 = _mm_add_ps(acc1, s1); w_idx += 8; s_idx += 2; } let temp4 = _mm_add_ps(acc0, acc1); let temp2 = _mm_hadd_ps(temp4, temp4); let temp1 = _mm_hadd_ps(temp2, temp2); let mut result = 0.0; _mm_store_ss(&mut result, temp1); result } } impl SseSample for f64 { type Sinc = __m128d; #[target_feature(enable = "sse3")] unsafe fn pack_sincs(sincs: Vec>) -> Vec> { let mut packed_sincs = Vec::new(); for sinc in sincs.iter() { let mut packed = Vec::new(); for elements in sinc.chunks(2) { let packed_elems = _mm_loadu_pd(&elements[0]); packed.push(packed_elems); } packed_sincs.push(packed); } packed_sincs } #[target_feature(enable = "sse3")] unsafe fn get_sinc_interpolated_unsafe( wave: &[f64], index: usize, subindex: usize, sincs: &[Vec], length: usize, ) -> f64 { let sinc = sincs.get_unchecked(subindex); let wave_cut = &wave[index..(index + length)]; let mut acc0 = _mm_setzero_pd(); let mut acc1 = _mm_setzero_pd(); let mut acc2 = _mm_setzero_pd(); let mut acc3 = _mm_setzero_pd(); let mut w_idx = 0; let mut s_idx = 0; for _ in 0..wave_cut.len() / 8 { let w0 = _mm_loadu_pd(wave_cut.get_unchecked(w_idx)); let w1 = _mm_loadu_pd(wave_cut.get_unchecked(w_idx + 2)); let w2 = _mm_loadu_pd(wave_cut.get_unchecked(w_idx + 4)); let w3 = _mm_loadu_pd(wave_cut.get_unchecked(w_idx + 6)); let s0 = _mm_mul_pd(w0, *sinc.get_unchecked(s_idx)); let s1 = _mm_mul_pd(w1, *sinc.get_unchecked(s_idx + 1)); let s2 = _mm_mul_pd(w2, *sinc.get_unchecked(s_idx + 2)); let s3 = _mm_mul_pd(w3, *sinc.get_unchecked(s_idx + 3)); acc0 = _mm_add_pd(acc0, s0); acc1 = _mm_add_pd(acc1, s1); acc2 = _mm_add_pd(acc2, s2); acc3 = _mm_add_pd(acc3, s3); w_idx += 8; s_idx += 4; } let temp2_0 = _mm_add_pd(acc0, acc1); let temp2_1 = _mm_add_pd(acc2, acc3); let temp2 = _mm_hadd_pd(temp2_0, temp2_1); let temp1 = _mm_hadd_pd(temp2, temp2); let mut result = 0.0; _mm_store_sd(&mut result, temp1); result } } /// A SSE accelerated interpolator. pub struct SseInterpolator where T: SseSample, { sincs: Vec>, length: usize, nbr_sincs: usize, } impl SincInterpolator for SseInterpolator where T: SseSample, { /// Calculate the scalar produt of an input wave and the selected sinc filter. fn get_sinc_interpolated(&self, wave: &[T], index: usize, subindex: usize) -> T { assert!( (index + self.length) < wave.len(), "Tried to interpolate for index {}, max for the given input is {}", index, wave.len() - self.length - 1 ); assert!( subindex < self.nbr_sincs, "Tried to use sinc subindex {}, max is {}", subindex, self.nbr_sincs - 1 ); unsafe { T::get_sinc_interpolated_unsafe(wave, index, subindex, &self.sincs, self.length) } } fn len(&self) -> usize { self.length } fn nbr_sincs(&self) -> usize { self.nbr_sincs } } impl SseInterpolator where T: Sample, { /// Create a new SseInterpolator. /// /// Parameters are: /// - `sinc_len`: Length of sinc functions. /// - `oversampling_factor`: Number of intermediate sincs (oversampling factor). /// - `f_cutoff`: Relative cutoff frequency. /// - `window`: Window function to use. pub fn new( sinc_len: usize, oversampling_factor: usize, f_cutoff: f32, window: WindowFunction, ) -> Result { if let Some(feature) = FEATURES.iter().find(|f| !f.is_detected()) { return Err(MissingCpuFeature(*feature)); } assert!(sinc_len % 8 == 0, "Sinc length must be a multiple of 8."); let sincs = make_sincs(sinc_len, oversampling_factor, f_cutoff, window); let sincs = unsafe { ::pack_sincs(sincs) }; Ok(Self { sincs, length: sinc_len, nbr_sincs: oversampling_factor, }) } } #[cfg(test)] mod tests { use crate::sinc::make_sincs; use crate::sinc_interpolator::sinc_interpolator_sse::SseInterpolator; use crate::sinc_interpolator::SincInterpolator; use crate::WindowFunction; use num_traits::Float; use rand::Rng; use test_log::test; fn get_sinc_interpolated(wave: &[T], index: usize, sinc: &[T]) -> T { let wave_cut = &wave[index..(index + sinc.len())]; wave_cut .iter() .zip(sinc.iter()) .fold(T::zero(), |acc, (x, y)| acc + *x * *y) } #[test] fn test_sse_interpolator_64() { let mut rng = rand::thread_rng(); let mut wave = Vec::new(); for _ in 0..2048 { wave.push(rng.gen::()); } let sinc_len = 256; let f_cutoff = 0.9473371669037001; let oversampling_factor = 256; let window = WindowFunction::BlackmanHarris2; let sincs = make_sincs::(sinc_len, oversampling_factor, f_cutoff, window); let interpolator = SseInterpolator::::new(sinc_len, oversampling_factor, f_cutoff, window).unwrap(); let value = interpolator.get_sinc_interpolated(&wave, 333, 123); let check = get_sinc_interpolated(&wave, 333, &sincs[123]); assert!((value - check).abs() < 1.0e-9); } #[test] fn test_sse_interpolator_32() { let mut rng = rand::thread_rng(); let mut wave = Vec::new(); for _ in 0..2048 { wave.push(rng.gen::()); } let sinc_len = 256; let f_cutoff = 0.9473371669037001; let oversampling_factor = 256; let window = WindowFunction::BlackmanHarris2; let sincs = make_sincs::(sinc_len, oversampling_factor, f_cutoff, window); let interpolator = SseInterpolator::::new(sinc_len, oversampling_factor, f_cutoff, window).unwrap(); let value = interpolator.get_sinc_interpolated(&wave, 333, 123); let check = get_sinc_interpolated(&wave, 333, &sincs[123]); assert!((value - check).abs() < 1.0e-5); } } rubato-0.16.2/src/synchro.rs000064400000000000000000001116061046102023000140350ustar 00000000000000use crate::error::ResamplerConstructionError; use crate::sinc::make_sincs; use crate::windows::WindowFunction; use num_complex::Complex; use num_integer as integer; use num_traits::Zero; use std::sync::Arc; use crate::error::{ResampleError, ResampleResult}; use crate::{calculate_cutoff, update_mask_from_buffers, validate_buffers, Resampler, Sample}; use realfft::{ComplexToReal, RealFftPlanner, RealToComplex}; /// A helper for resampling a single chunk of data. struct FftResampler { fft_size_in: usize, fft_size_out: usize, filter_f: Vec>, fft: Arc>, ifft: Arc>, scratch_fw: Vec>, scratch_inv: Vec>, input_buf: Vec, input_f: Vec>, output_f: Vec>, output_buf: Vec, } /// A synchronous resampler that needs a fixed number of audio frames for input /// and returns a variable number of frames. /// /// The resampling is done by FFT:ing the input data. The spectrum is then extended or /// truncated as well as multiplied with an antialiasing filter /// before it's inverse transformed to get the resampled waveforms. pub struct FftFixedIn { nbr_channels: usize, chunk_size_in: usize, fft_size_in: usize, fft_size_out: usize, overlaps: Vec>, input_buffers: Vec>, channel_mask: Vec, saved_frames: usize, resampler: FftResampler, } /// A synchronous resampler that needs a varying number of audio frames for input /// and returns a fixed number of frames. /// /// The resampling is done by FFT:ing the input data. The spectrum is then extended or /// truncated as well as multiplied with an antialiasing filter /// before it's inverse transformed to get the resampled waveforms. pub struct FftFixedOut { nbr_channels: usize, chunk_size_out: usize, fft_size_in: usize, fft_size_out: usize, overlaps: Vec>, output_buffers: Vec>, channel_mask: Vec, saved_frames: usize, frames_needed: usize, resampler: FftResampler, } /// A synchronous resampler that accepts a fixed number of audio frames for input /// and returns a fixed number of frames. /// /// The resampling is done by FFT:ing the input data. The spectrum is then extended or /// truncated as well as multiplied with an antialiasing filter /// before it's inverse transformed to get the resampled waveforms. pub struct FftFixedInOut { nbr_channels: usize, chunk_size_in: usize, chunk_size_out: usize, fft_size_in: usize, channel_mask: Vec, overlaps: Vec>, resampler: FftResampler, } fn validate_sample_rates(input: usize, output: usize) -> Result<(), ResamplerConstructionError> { if input == 0 || output == 0 { return Err(ResamplerConstructionError::InvalidSampleRate { input, output }); } Ok(()) } impl FftResampler where T: Sample, { // pub fn new(fft_size_in: usize, fft_size_out: usize) -> Self { // calculate antialiasing cutoff let cutoff = if fft_size_in > fft_size_out { calculate_cutoff::(fft_size_out, WindowFunction::BlackmanHarris2) * fft_size_out as f32 / fft_size_in as f32 } else { calculate_cutoff::(fft_size_in, WindowFunction::BlackmanHarris2) }; debug!( "Create new FftResampler, fft_size_in: {}, fft_size_out: {}, cutoff: {}", fft_size_in, fft_size_out, cutoff ); let sinc = make_sincs::(fft_size_in, 1, cutoff, WindowFunction::BlackmanHarris2); let mut filter_t: Vec = vec![T::zero(); 2 * fft_size_in]; let mut filter_f: Vec> = vec![Complex::zero(); fft_size_in + 1]; for (n, f) in filter_t.iter_mut().enumerate().take(fft_size_in) { *f = sinc[0][n] / T::coerce(2 * fft_size_in); } let input_f: Vec> = vec![Complex::zero(); fft_size_in + 1]; let input_buf: Vec = vec![T::zero(); 2 * fft_size_in]; let output_f: Vec> = vec![Complex::zero(); fft_size_out + 1]; let output_buf: Vec = vec![T::zero(); 2 * fft_size_out]; let mut planner = RealFftPlanner::::new(); let fft = planner.plan_fft_forward(2 * fft_size_in); let ifft = planner.plan_fft_inverse(2 * fft_size_out); fft.process(&mut filter_t, &mut filter_f).unwrap(); let scratch_fw = fft.make_scratch_vec(); let scratch_inv = ifft.make_scratch_vec(); FftResampler { fft_size_in, fft_size_out, filter_f, fft, ifft, scratch_fw, scratch_inv, input_buf, input_f, output_f, output_buf, } } /// Resample a small chunk. fn resample_unit(&mut self, wave_in: &[T], wave_out: &mut [T], overlap: &mut [T]) { // Copy to input buffer and clear padding area. self.input_buf[0..self.fft_size_in].copy_from_slice(wave_in); for item in self .input_buf .iter_mut() .skip(self.fft_size_in) .take(self.fft_size_in) { *item = T::zero(); } // FFT and store result in history, update index. self.fft .process_with_scratch(&mut self.input_buf, &mut self.input_f, &mut self.scratch_fw) .unwrap(); let new_len = if self.fft_size_in < self.fft_size_out { self.fft_size_in + 1 } else { self.fft_size_out }; // Multiply with filter FT. self.input_f .iter_mut() .take(new_len) .zip(self.filter_f.iter()) .for_each(|(spec, filt)| *spec *= filt); // copy to modified spectrum self.output_f[0..new_len].copy_from_slice(&self.input_f[0..new_len]); for val in self.output_f[new_len..].iter_mut() { *val = Complex::zero(); } // IFFT result, store result and overlap. self.ifft .process_with_scratch( &mut self.output_f, &mut self.output_buf, &mut self.scratch_inv, ) .unwrap(); for (n, item) in wave_out.iter_mut().enumerate().take(self.fft_size_out) { *item = self.output_buf[n] + overlap[n]; } overlap.copy_from_slice(&self.output_buf[self.fft_size_out..]); } } impl FftFixedInOut where T: Sample, { /// Create a new FftFixedInOut. /// /// Parameters are: /// - `sample_rate_input`: Input sample rate, must be > 0. /// - `sample_rate_output`: Output sample rate, must be > 0. /// - `chunk_size_in`: desired length of input data in frames, actual value may be different. /// - `nbr_channels`: number of channels in input/output. pub fn new( sample_rate_input: usize, sample_rate_output: usize, chunk_size_in: usize, nbr_channels: usize, ) -> Result { validate_sample_rates(sample_rate_input, sample_rate_output)?; debug!( "Create new FftFixedInOut, sample_rate_input: {}, sample_rate_output: {} chunk_size_in: {}, channels: {}", sample_rate_input, sample_rate_output, chunk_size_in, nbr_channels ); let gcd = integer::gcd(sample_rate_input, sample_rate_output); let min_chunk_in = sample_rate_input / gcd; let fft_chunks = (chunk_size_in as f32 / min_chunk_in as f32).ceil() as usize; let fft_size_out = fft_chunks * sample_rate_output / gcd; let fft_size_in = fft_chunks * sample_rate_input / gcd; let resampler = FftResampler::::new(fft_size_in, fft_size_out); let overlaps: Vec> = vec![vec![T::zero(); fft_size_out]; nbr_channels]; let channel_mask = vec![true; nbr_channels]; Ok(FftFixedInOut { nbr_channels, chunk_size_in: fft_size_in, chunk_size_out: fft_size_out, fft_size_in, overlaps, resampler, channel_mask, }) } } impl Resampler for FftFixedInOut where T: Sample, { fn process_into_buffer, Vout: AsMut<[T]>>( &mut self, wave_in: &[Vin], wave_out: &mut [Vout], active_channels_mask: Option<&[bool]>, ) -> ResampleResult<(usize, usize)> { if let Some(mask) = active_channels_mask { self.channel_mask.copy_from_slice(mask); } else { update_mask_from_buffers(&mut self.channel_mask); }; validate_buffers( wave_in, wave_out, &self.channel_mask, self.nbr_channels, self.chunk_size_in, self.chunk_size_out, )?; for (channel, active) in self.channel_mask.iter().enumerate() { if *active { self.resampler.resample_unit( &wave_in[channel].as_ref()[..self.chunk_size_in], &mut wave_out[channel].as_mut()[..self.chunk_size_out], &mut self.overlaps[channel], ) } } Ok((self.chunk_size_in, self.chunk_size_out)) } fn input_frames_max(&self) -> usize { self.fft_size_in } fn input_frames_next(&self) -> usize { self.fft_size_in } fn nbr_channels(&self) -> usize { self.nbr_channels } fn output_frames_max(&self) -> usize { self.chunk_size_out } fn output_frames_next(&self) -> usize { self.output_frames_max() } fn output_delay(&self) -> usize { self.chunk_size_out / 2 } /// Update the resample ratio. This is not supported by this resampler and /// always returns an [ResampleError::SyncNotAdjustable]. fn set_resample_ratio(&mut self, _new_ratio: f64, _ramp: bool) -> ResampleResult<()> { Err(ResampleError::SyncNotAdjustable) } /// Update the resample ratio relative to the original one. This is not /// supported by this resampler and always returns an [ResampleError::SyncNotAdjustable]. fn set_resample_ratio_relative(&mut self, _rel_ratio: f64, _ramp: bool) -> ResampleResult<()> { Err(ResampleError::SyncNotAdjustable) } fn reset(&mut self) { self.overlaps .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero())); self.channel_mask.iter_mut().for_each(|val| *val = true); } } impl FftFixedOut where T: Sample, { /// Create a new FftFixedOut. /// /// Parameters are: /// - `sample_rate_input`: Input sample rate, must be > 0. /// - `sample_rate_output`: Output sample rate, must be > 0. /// - `chunk_size_out`: length of output data in frames. /// - `sub_chunks`: desired number of subchunks for processing, actual number may be different. /// - `nbr_channels`: number of channels in input/output. pub fn new( sample_rate_input: usize, sample_rate_output: usize, chunk_size_out: usize, sub_chunks: usize, nbr_channels: usize, ) -> Result { validate_sample_rates(sample_rate_input, sample_rate_output)?; let gcd = integer::gcd(sample_rate_input, sample_rate_output); let min_chunk_out = sample_rate_output / gcd; let wanted_subsize = chunk_size_out / sub_chunks; let fft_chunks = (wanted_subsize as f32 / min_chunk_out as f32).ceil() as usize; let fft_size_out = fft_chunks * sample_rate_output / gcd; let fft_size_in = fft_chunks * sample_rate_input / gcd; let resampler = FftResampler::::new(fft_size_in, fft_size_out); debug!( "Create new FftFixedOut, sample_rate_input: {}, sample_rate_output: {} chunk_size_in: {}, channels: {}, fft_size_in: {}, fft_size_out: {}", sample_rate_input, sample_rate_output, chunk_size_out, nbr_channels, fft_size_in, fft_size_out ); let overlaps: Vec> = vec![vec![T::zero(); fft_size_out]; nbr_channels]; let output_buffers: Vec> = vec![vec![T::zero(); chunk_size_out + fft_size_out]; nbr_channels]; let channel_mask = vec![true; nbr_channels]; let saved_frames = 0; let chunks_needed = (chunk_size_out as f32 / fft_size_out as f32).ceil() as usize; let frames_needed = chunks_needed * fft_size_in; Ok(FftFixedOut { nbr_channels, chunk_size_out, fft_size_in, fft_size_out, overlaps, output_buffers, saved_frames, frames_needed, resampler, channel_mask, }) } } impl Resampler for FftFixedOut where T: Sample, { fn process_into_buffer, Vout: AsMut<[T]>>( &mut self, wave_in: &[Vin], wave_out: &mut [Vout], active_channels_mask: Option<&[bool]>, ) -> ResampleResult<(usize, usize)> { if let Some(mask) = active_channels_mask { self.channel_mask.copy_from_slice(mask); } else { update_mask_from_buffers(&mut self.channel_mask); }; validate_buffers( wave_in, wave_out, &self.channel_mask, self.nbr_channels, self.frames_needed, self.chunk_size_out, )?; for (chan, active) in self.channel_mask.iter().enumerate() { if *active { debug_assert!(self.chunk_size_out <= wave_out[chan].as_mut().len()); for (in_chunk, out_chunk) in wave_in[chan].as_ref()[..self.frames_needed] .chunks(self.fft_size_in) .zip( self.output_buffers[chan][self.saved_frames..] .chunks_mut(self.fft_size_out), ) { self.resampler .resample_unit(in_chunk, out_chunk, &mut self.overlaps[chan]); } } } let processed_frames = self.saved_frames + self.fft_size_out * (self.frames_needed / self.fft_size_in); // Copy to output, and save extra frames for next round. if processed_frames >= self.chunk_size_out { self.saved_frames = processed_frames - self.chunk_size_out; for (chan, active) in self.channel_mask.iter().enumerate() { if *active { wave_out[chan].as_mut()[..self.chunk_size_out] .copy_from_slice(&self.output_buffers[chan][..self.chunk_size_out]); self.output_buffers[chan].copy_within( self.chunk_size_out..(self.chunk_size_out + self.saved_frames), 0, ); } } } else { self.saved_frames = processed_frames; } // Calculate number of needed frames from next round. let frames_needed_out = if self.chunk_size_out > self.saved_frames { self.chunk_size_out - self.saved_frames } else { 0 }; let input_frames_used = self.frames_needed; let chunks_needed = (frames_needed_out as f32 / self.fft_size_out as f32).ceil() as usize; self.frames_needed = chunks_needed * self.fft_size_in; Ok((input_frames_used, self.chunk_size_out)) } fn input_frames_max(&self) -> usize { (self.chunk_size_out as f32 / self.fft_size_out as f32).ceil() as usize * self.fft_size_in } fn input_frames_next(&self) -> usize { self.frames_needed } fn nbr_channels(&self) -> usize { self.nbr_channels } fn output_frames_max(&self) -> usize { self.chunk_size_out } fn output_frames_next(&self) -> usize { self.output_frames_max() } fn output_delay(&self) -> usize { self.fft_size_out / 2 } /// Update the resample ratio. This is not supported by this resampler and /// always returns [ResampleError::SyncNotAdjustable]. fn set_resample_ratio(&mut self, _new_ratio: f64, _ramp: bool) -> ResampleResult<()> { Err(ResampleError::SyncNotAdjustable) } /// Update the resample ratio relative to the original one. This is not /// supported by this resampler and always returns [ResampleError::SyncNotAdjustable]. fn set_resample_ratio_relative(&mut self, _rel_ratio: f64, _ramp: bool) -> ResampleResult<()> { Err(ResampleError::SyncNotAdjustable) } fn reset(&mut self) { self.overlaps .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero())); self.output_buffers .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero())); self.channel_mask.iter_mut().for_each(|val| *val = true); self.saved_frames = 0; let chunks_needed = (self.chunk_size_out as f32 / self.fft_size_out as f32).ceil() as usize; self.frames_needed = chunks_needed * self.fft_size_in; } } impl FftFixedIn where T: Sample, { /// Create a new FftFixedIn. /// /// Parameters are: /// - `sample_rate_input`: Input sample rate, must be > 0. /// - `sample_rate_output`: Output sample rate, must be > 0. /// - `chunk_size_in`: length of input data in frames. /// - `sub_chunks`: desired number of subchunks for processing, actual number used may be different. /// - `nbr_channels`: number of channels in input/output. pub fn new( sample_rate_input: usize, sample_rate_output: usize, chunk_size_in: usize, sub_chunks: usize, nbr_channels: usize, ) -> Result { validate_sample_rates(sample_rate_input, sample_rate_output)?; let gcd = integer::gcd(sample_rate_input, sample_rate_output); let min_chunk_in = sample_rate_input / gcd; let wanted_subsize = chunk_size_in / sub_chunks; let fft_chunks = (wanted_subsize as f32 / min_chunk_in as f32).ceil() as usize; let fft_size_out = fft_chunks * sample_rate_output / gcd; let fft_size_in = fft_chunks * sample_rate_input / gcd; let resampler = FftResampler::::new(fft_size_in, fft_size_out); debug!( "Create new FftFixedOut, sample_rate_input: {}, sample_rate_output: {} chunk_size_in: {}, channels: {}, fft_size_in: {}, fft_size_out: {}", sample_rate_input, sample_rate_output, chunk_size_in, nbr_channels, fft_size_in, fft_size_out ); let overlaps: Vec> = vec![vec![T::zero(); fft_size_out]; nbr_channels]; let input_buffers: Vec> = vec![vec![T::zero(); chunk_size_in + fft_size_in]; nbr_channels]; let channel_mask = vec![true; nbr_channels]; let saved_frames = 0; Ok(FftFixedIn { nbr_channels, chunk_size_in, fft_size_in, fft_size_out, overlaps, input_buffers, saved_frames, resampler, channel_mask, }) } } impl Resampler for FftFixedIn where T: Sample, { fn process_into_buffer, Vout: AsMut<[T]>>( &mut self, wave_in: &[Vin], wave_out: &mut [Vout], active_channels_mask: Option<&[bool]>, ) -> ResampleResult<(usize, usize)> { if let Some(mask) = active_channels_mask { self.channel_mask.copy_from_slice(mask); } else { update_mask_from_buffers(&mut self.channel_mask); }; let next_saved_frames = self.saved_frames + self.chunk_size_in; let nbr_chunks_ready = (next_saved_frames as f32 / self.fft_size_in as f32).floor() as usize; let needed_len = nbr_chunks_ready * self.fft_size_out; validate_buffers( wave_in, wave_out, &self.channel_mask, self.nbr_channels, self.chunk_size_in, needed_len, )?; // Copy new samples to input buffer. for (chan, active) in self.channel_mask.iter().enumerate() { if *active { for (input, buffer) in wave_in[chan].as_ref().iter().zip( self.input_buffers[chan] .iter_mut() .skip(self.saved_frames) .take(self.chunk_size_in), ) { *buffer = *input; } } } self.saved_frames = next_saved_frames; for (chan, active) in self.channel_mask.iter().enumerate() { if *active { debug_assert!(needed_len <= wave_out[chan].as_mut().len()); for (in_chunk, out_chunk) in self.input_buffers[chan] .chunks(self.fft_size_in) .take(nbr_chunks_ready) .zip(wave_out[chan].as_mut().chunks_mut(self.fft_size_out)) { self.resampler .resample_unit(in_chunk, out_chunk, &mut self.overlaps[chan]); } } } // Save extra frames for next round. let frames_in_used = nbr_chunks_ready * self.fft_size_in; let extra = self.saved_frames - frames_in_used; if self.saved_frames > frames_in_used { for (chan, active) in self.channel_mask.iter().enumerate() { if *active { self.input_buffers[chan].copy_within(frames_in_used..self.saved_frames, 0); } } } self.saved_frames = extra; Ok((self.chunk_size_in, needed_len)) } fn input_frames_max(&self) -> usize { self.chunk_size_in } fn input_frames_next(&self) -> usize { self.chunk_size_in } fn nbr_channels(&self) -> usize { self.nbr_channels } fn output_frames_max(&self) -> usize { let max_stored_frames = self.fft_size_in - 1; let max_available_frames = max_stored_frames + self.chunk_size_in; let max_subchunks_to_process = max_available_frames / self.fft_size_in; max_subchunks_to_process * self.fft_size_out } fn output_frames_next(&self) -> usize { (((self.saved_frames + self.chunk_size_in) as f32) / self.fft_size_in as f32).floor() as usize * self.fft_size_out } fn output_delay(&self) -> usize { self.fft_size_out / 2 } /// Update the resample ratio. This is not supported by this resampler and /// always returns [ResampleError::SyncNotAdjustable]. fn set_resample_ratio(&mut self, _new_ratio: f64, _ramp: bool) -> ResampleResult<()> { Err(ResampleError::SyncNotAdjustable) } /// Update the resample ratio relative to the original one. This is not /// supported by this resampler and always returns [ResampleError::SyncNotAdjustable]. fn set_resample_ratio_relative(&mut self, _rel_ratio: f64, _ramp: bool) -> ResampleResult<()> { Err(ResampleError::SyncNotAdjustable) } fn reset(&mut self) { self.overlaps .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero())); self.input_buffers .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero())); self.channel_mask.iter_mut().for_each(|val| *val = true); self.saved_frames = 0; } } #[cfg(test)] mod tests { use crate::check_output; use crate::synchro::{FftFixedIn, FftFixedInOut, FftFixedOut, FftResampler}; use crate::Resampler; use rand::Rng; use test_log::test; #[test] fn resample_unit() { let mut resampler = FftResampler::::new(147, 1000); let mut wave_in = vec![0.0; 147]; wave_in[0] = 0.3; wave_in[1] = 0.7; wave_in[2] = 1.0; wave_in[3] = 1.0; wave_in[4] = 0.7; wave_in[5] = 0.3; let mut wave_out = vec![0.0; 1000]; let mut overlap = vec![0.0; 1000]; resampler.resample_unit(&wave_in, &mut wave_out, &mut overlap); let vecsum = wave_out.iter().sum::(); let maxval = wave_out.iter().cloned().fold(0. / 0., f64::max); assert!((vecsum - 4.0 * 1000.0 / 147.0).abs() < 1.0e-6); assert!((maxval - 1.0).abs() < 0.1); } #[test] fn make_resampler_fio() { // asking for 1024 give the nearest which is 1029 -> 1120 let mut resampler = FftFixedInOut::::new(44100, 48000, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 1120); } #[test] fn reset_resampler_fio() { let mut resampler = FftFixedInOut::::new(44100, 48000, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); let mut rng = rand::thread_rng(); let mut waves = vec![vec![0.0f64; frames]; 2]; waves .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = rng.gen())); let out1 = resampler.process(&waves, None).unwrap(); resampler.reset(); assert_eq!( frames, resampler.input_frames_next(), "Resampler requires different number of frames when new and after a reset." ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!( out1, out2, "Resampler gives different output when new and after a reset." ); } #[test] fn make_resampler_fio_skipped() { // Asking for 1024 give the nearest which is 1029 -> 1120. let mut resampler = FftFixedInOut::::new(44100, 48000, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); let waves = vec![vec![0.0f64; frames], Vec::new()]; let mask = vec![true, false]; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 1120); assert!(out[1].is_empty()); } #[test] fn make_resampler_fo() { let mut resampler = FftFixedOut::::new(44100, 192000, 1024, 2, 2).unwrap(); let frames = resampler.input_frames_next(); assert_eq!(frames, 294); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 1024); } #[test] fn reset_resampler_fo() { let mut resampler = FftFixedOut::::new(44100, 192000, 1024, 2, 2).unwrap(); let frames = resampler.input_frames_next(); let mut rng = rand::thread_rng(); let mut waves = vec![vec![0.0f64; frames]; 2]; waves .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = rng.gen())); let out1 = resampler.process(&waves, None).unwrap(); resampler.reset(); assert_eq!( frames, resampler.input_frames_next(), "Resampler requires different number of frames when new and after a reset." ); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!( out1, out2, "Resampler gives different output when new and after a reset." ); } #[test] fn make_resampler_fo_skipped() { let mut resampler = FftFixedOut::::new(44100, 192000, 1024, 2, 2).unwrap(); let frames = resampler.input_frames_next(); assert_eq!(frames, 294); let waves = vec![vec![0.0f64; frames], Vec::new()]; let mask = vec![true, false]; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 1024); assert!(out[1].is_empty()); } #[test] fn make_resampler_fo_empty() { let mut resampler = FftFixedOut::::new(44100, 192000, 1024, 2, 2).unwrap(); let frames = resampler.input_frames_next(); assert_eq!(frames, 294); let waves = vec![Vec::new(); 2]; let mask = vec![false; 2]; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert!(out[0].is_empty()); assert!(out[1].is_empty()); } #[test] fn make_resampler_fi() { let mut resampler = FftFixedIn::::new(44100, 48000, 1024, 2, 2).unwrap(); let frames = resampler.input_frames_next(); assert_eq!(frames, 1024); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 640); } #[test] fn reset_resampler_fi() { let mut resampler = FftFixedIn::::new(44100, 48000, 1024, 2, 2).unwrap(); let mut rng = rand::thread_rng(); let mut waves = vec![vec![0.0f64; 1024]; 2]; waves .iter_mut() .for_each(|ch| ch.iter_mut().for_each(|s| *s = rng.gen())); let out1 = resampler.process(&waves, None).unwrap(); resampler.reset(); let out2 = resampler.process(&waves, None).unwrap(); assert_eq!( out1, out2, "Resampler gives different output when new and after a reset." ); } #[test] fn make_resampler_fi_noalloc() { let mut resampler = FftFixedIn::::new(44100, 48000, 1024, 2, 2).unwrap(); let frames = resampler.input_frames_next(); assert_eq!(frames, 1024); let waves = vec![vec![0.0f64; frames]; 2]; let mut out = vec![vec![0.0f64; 2 * frames]; 2]; let allocated_out_len = out[0].len(); assert_eq!(allocated_out_len, out[1].len()); let mask = vec![true; 2]; let (consumed_in_len, processed_out_len) = resampler .process_into_buffer(&waves, &mut out, Some(&mask)) .unwrap(); assert_eq!(out.len(), 2); assert_eq!(consumed_in_len, frames); assert_eq!(processed_out_len, 640); // The vectors are not truncated during processing. assert_eq!(allocated_out_len, out[0].len()); assert_eq!(allocated_out_len, out[1].len()); } #[test] fn make_resampler_fi_downsample() { let mut resampler = FftFixedIn::::new(48000, 16000, 1200, 2, 2).unwrap(); let frames = resampler.input_frames_next(); assert_eq!(frames, 1200); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 400); } #[test] fn make_resampler_fi_skipped() { let mut resampler = FftFixedIn::::new(44100, 48000, 1024, 2, 2).unwrap(); let frames = resampler.input_frames_next(); assert_eq!(frames, 1024); let waves = vec![vec![0.0f64; frames], Vec::new()]; let mask = vec![true, false]; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 640); assert!(out[1].is_empty()); } #[test] fn make_resampler_fi_empty() { let mut resampler = FftFixedIn::::new(44100, 48000, 1024, 2, 2).unwrap(); let frames = resampler.input_frames_next(); assert_eq!(frames, 1024); let waves = vec![Vec::new(); 2]; let mask = vec![false; 2]; let out = resampler.process(&waves, Some(&mask)).unwrap(); assert_eq!(out.len(), 2); assert!(out[0].is_empty()); assert!(out[1].is_empty()); } #[test] fn make_resampler_fio_unusualratio() { // Asking for 1024 give the nearest which is 1029 -> 1120. let mut resampler = FftFixedInOut::::new(44100, 44110, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 4411); } #[test] fn make_resampler_fo_unusualratio() { let mut resampler = FftFixedOut::::new(44100, 44110, 1024, 2, 2).unwrap(); let frames = resampler.input_frames_next(); assert_eq!(frames, 4410); let waves = vec![vec![0.0f64; frames]; 2]; let out = resampler.process(&waves, None).unwrap(); assert_eq!(out.len(), 2); assert_eq!(out[0].len(), 1024); } #[test] fn check_fo_output() { let mut resampler = FftFixedOut::::new(44100, 48000, 4096, 4, 2).unwrap(); check_output!(resampler); } #[test] fn check_fi_output() { let mut resampler = FftFixedIn::::new(44100, 48000, 4096, 4, 2).unwrap(); check_output!(resampler); } #[test] fn check_fio_output() { let mut resampler = FftFixedInOut::::new(44100, 48000, 4096, 2).unwrap(); check_output!(resampler); } #[test] fn check_fi_max_output_length() { // parameters: // - rate in // - rate out // - requested chunksize // - requested number of subchunks // - expected fft input length // - expected fft output length let params_to_test = [ // fft sizes < chunksize [44100, 48000, 1024, 4, 294, 320], [48000, 44100, 1024, 4, 320, 294], // fft sizes << chunksize [44000, 48000, 1024, 100, 11, 12], // fft sizes > chunksize [32728, 32000, 1024, 4, 4091, 4000], [32000, 32728, 1024, 4, 4000, 4091], // fft sizes >> chunksize [37199, 39119, 1024, 4, 37199, 39119], [39119, 37199, 1024, 4, 39119, 37199], ]; for params in params_to_test { println!("params: {:?}", params); let [rate_in, rate_out, chunksize, subchunks, fft_in_len, fft_out_len] = params; let resampler = FftFixedIn::::new(rate_in, rate_out, chunksize, subchunks, 1).unwrap(); assert_eq!(resampler.fft_size_in, fft_in_len); assert_eq!(resampler.fft_size_out, fft_out_len); let resampler_max_output_len = resampler.output_frames_max(); println!( "Resampler reports max output length: {}", resampler_max_output_len ); assert!(resampler.output_frames_max() >= fft_out_len); // expected length let max_stored_frames = fft_in_len - 1; let max_available_samples = max_stored_frames + chunksize; let max_subchunks_to_process = max_available_samples / fft_in_len; let expected_max_out_len = max_subchunks_to_process * fft_out_len; println!("Max stored frames: {}, max avail frames: {}, max ready subchunks: {}, expected max output len: {}", max_stored_frames, max_available_samples, max_subchunks_to_process, expected_max_out_len); assert_eq!(resampler.output_frames_max(), expected_max_out_len); } } #[test] fn check_fo_max_input_length() { // parameters: // - rate in // - rate out // - requested chunksize // - requested number of subchunks // - expected fft input length // - expected fft output length let params_to_test = [ // fft sizes < chunksize [44100, 48000, 1024, 4, 294, 320], [48000, 44100, 1024, 4, 320, 294], // fft sizes << chunksize [44000, 48000, 1024, 100, 11, 12], // fft sizes > chunksize [32728, 32000, 1024, 4, 4091, 4000], [32000, 32728, 1024, 4, 4000, 4091], // fft sizes >> chunksize [37199, 39119, 1024, 4, 37199, 39119], [39119, 37199, 1024, 4, 39119, 37199], ]; for params in params_to_test { println!("params: {:?}", params); let [rate_in, rate_out, chunksize, subchunks, fft_in_len, fft_out_len] = params; let resampler = FftFixedOut::::new(rate_in, rate_out, chunksize, subchunks, 1).unwrap(); assert_eq!(resampler.fft_size_in, fft_in_len); assert_eq!(resampler.fft_size_out, fft_out_len); let resampler_max_input_len = resampler.input_frames_max(); println!( "Resampler reports max input length: {}", resampler_max_input_len ); assert!(resampler.input_frames_max() >= fft_in_len); // max needed is when we have none stored let max_frames_needed = chunksize; let max_subchunks_needed = (max_frames_needed as f32 / fft_out_len as f32).ceil() as usize; let expected_max_in_len = max_subchunks_needed * fft_in_len; println!( "Max frames needed: {}, max subchunks_needed: {}, expected max input len: {}", max_frames_needed, max_subchunks_needed, expected_max_in_len ); assert_eq!(resampler.input_frames_max(), expected_max_in_len); } } } rubato-0.16.2/src/windows.rs000064400000000000000000000200751046102023000140410ustar 00000000000000use crate::Sample; /// Different window functions that can be used to window the sinc function. #[derive(Debug, Clone, Copy)] pub enum WindowFunction { /// Blackman. Intermediate rolloff and intermediate attenuation. Blackman, /// Squared Blackman. Slower rolloff but better attenuation than Blackman. Blackman2, /// Blackman-Harris. Slow rolloff but good attenuation. BlackmanHarris, /// Squared Blackman-Harris. Slower rolloff but better attenuation than Blackman-Harris. BlackmanHarris2, /// Hann. Fast rolloff but not very high attenuation. Hann, /// Squared Hann. Slower rolloff and higher attenuation than simple Hann. Hann2, } /// Helper function. Standard Blackman-Harris window. // The window created is periodic. pub fn blackman_harris(npoints: usize) -> Vec where T: Sample, { trace!("Making a BlackmanHarris windows with {} points", npoints); let mut window = vec![T::zero(); npoints]; let pi2 = T::coerce(2.0) * T::PI; let pi4 = T::coerce(4.0) * T::PI; let pi6 = T::coerce(6.0) * T::PI; let np_f = T::coerce(npoints); let a = T::coerce(0.35875); let b = T::coerce(0.48829); let c = T::coerce(0.14128); let d = T::coerce(0.01168); for (x, item) in window.iter_mut().enumerate() { let x_float = T::coerce(x); *item = a - b * (pi2 * x_float / np_f).cos() + c * (pi4 * x_float / np_f).cos() - d * (pi6 * x_float / np_f).cos(); } window } /// Helper function. Standard Blackman window. // The window created is periodic. pub fn blackman(npoints: usize) -> Vec where T: Sample, { trace!("Making a Blackman windows with {} points", npoints); let mut window = vec![T::zero(); npoints]; let pi2 = T::coerce(2.0) * T::PI; let pi4 = T::coerce(4.0) * T::PI; let np_f = T::coerce(npoints); let a = T::coerce(0.42); let b = T::coerce(0.5); let c = T::coerce(0.08); for (x, item) in window.iter_mut().enumerate() { let x_float = T::coerce(x); *item = a - b * (pi2 * x_float / np_f).cos() + c * (pi4 * x_float / np_f).cos(); } window } /// Helper function. Standard Hann window. // The window created is periodic. pub fn hann(npoints: usize) -> Vec where T: Sample, { trace!("Making a Hann windows with {} points", npoints); let mut window = vec![T::zero(); npoints]; let pi2 = T::coerce(2.0) * T::PI; let np_f = T::coerce(npoints); let a = T::coerce(0.5); for (x, item) in window.iter_mut().enumerate() { let x_float = T::coerce(x); *item = a - a * (pi2 * x_float / np_f).cos(); } window } /// Make the selected window function. pub fn make_window(npoints: usize, windowfunc: WindowFunction) -> Vec where T: Sample, { let mut window = match windowfunc { WindowFunction::BlackmanHarris | WindowFunction::BlackmanHarris2 => { blackman_harris::(npoints) } WindowFunction::Blackman | WindowFunction::Blackman2 => blackman::(npoints), WindowFunction::Hann | WindowFunction::Hann2 => hann::(npoints), }; match windowfunc { WindowFunction::Blackman2 | WindowFunction::BlackmanHarris2 | WindowFunction::Hann2 => { window.iter_mut().for_each(|y| *y = *y * *y); } _ => {} }; window } /// Calculate a suitable relative cutoff frequency for the given sinc length using the given window function. /// The result is based on an approximation, which gives good results for sinc lengths from 32 to 2048. pub fn calculate_cutoff(npoints: usize, windowfunc: WindowFunction) -> T where T: Sample, { // Coefficient values generated by cutoff_fit_cubic.py let (k1, k2, k3) = match windowfunc { WindowFunction::BlackmanHarris => ( T::coerce(8.041443677716476), T::coerce(55.9506779343387), T::coerce(898.0287985384213), ), WindowFunction::BlackmanHarris2 => ( T::coerce(13.745202940783823), T::coerce(121.73532586374934), T::coerce(5964.163279612051), ), WindowFunction::Blackman => ( T::coerce(6.159598046201173), T::coerce(18.926415097606878), T::coerce(653.4247430458968), ), WindowFunction::Blackman2 => ( T::coerce(9.506235102129398), T::coerce(79.13120634953742), T::coerce(1502.2316160588925), ), WindowFunction::Hann => ( T::coerce(3.3481080887677166), T::coerce(10.106519434875038), T::coerce(78.96345249024414), ), WindowFunction::Hann2 => ( T::coerce(5.38751148378734), T::coerce(29.69451915489501), T::coerce(184.82117462266237), ), }; let one = T::one(); let npoints_t = T::coerce(npoints); one / (k1 / npoints_t + k2 / (npoints_t * npoints_t) + k3 / (npoints_t * npoints_t * npoints_t) + one) } #[cfg(test)] mod tests { extern crate approx; use crate::windows::blackman; use crate::windows::blackman_harris; use crate::windows::calculate_cutoff; use crate::windows::hann; use crate::windows::make_window; use crate::windows::WindowFunction; use approx::assert_abs_diff_eq; use test_log::test; #[test] fn test_blackman_harris() { let wnd = blackman_harris::(16); assert_abs_diff_eq!(wnd[8], 1.0, epsilon = 0.000001); assert!(wnd[0] < 0.001); assert!(wnd[15] < 0.1); } #[test] fn test_blackman() { let wnd = blackman::(16); assert_abs_diff_eq!(wnd[8], 1.0, epsilon = 0.000001); assert!(wnd[0] < 0.000001); assert!(wnd[15] < 0.1); } #[test] fn test_blackman2() { let wnd = make_window::(16, WindowFunction::Blackman); let wnd2 = make_window::(16, WindowFunction::Blackman2); assert_abs_diff_eq!(wnd[1] * wnd[1], wnd2[1], epsilon = 0.000001); assert_abs_diff_eq!(wnd[4] * wnd[4], wnd2[4], epsilon = 0.000001); assert_abs_diff_eq!(wnd[7] * wnd[7], wnd2[7], epsilon = 0.000001); assert!(wnd2[1] > 0.000001); assert!(wnd2[4] > 0.000001); assert!(wnd2[7] > 0.000001); } #[test] fn test_hann() { let wnd = hann::(16); assert_abs_diff_eq!(wnd[8], 1.0, epsilon = 0.000001); assert!(wnd[0] < 0.000001); assert!(wnd[15] < 0.1); } #[test] fn test_cutoff() { let cutoff = calculate_cutoff::(128, WindowFunction::Blackman); assert_abs_diff_eq!(cutoff, 0.953, epsilon = 0.001); let cutoff = calculate_cutoff::(256, WindowFunction::Blackman); assert_abs_diff_eq!(cutoff, 0.976, epsilon = 0.001); let cutoff = calculate_cutoff::(128, WindowFunction::Blackman2); assert_abs_diff_eq!(cutoff, 0.926, epsilon = 0.001); let cutoff = calculate_cutoff::(256, WindowFunction::Blackman2); assert_abs_diff_eq!(cutoff, 0.963, epsilon = 0.001); let cutoff = calculate_cutoff::(128, WindowFunction::BlackmanHarris); assert_abs_diff_eq!(cutoff, 0.937, epsilon = 0.001); let cutoff = calculate_cutoff::(256, WindowFunction::BlackmanHarris); assert_abs_diff_eq!(cutoff, 0.969, epsilon = 0.001); let cutoff = calculate_cutoff::(128, WindowFunction::BlackmanHarris2); assert_abs_diff_eq!(cutoff, 0.894, epsilon = 0.001); let cutoff = calculate_cutoff::(256, WindowFunction::BlackmanHarris2); assert_abs_diff_eq!(cutoff, 0.947, epsilon = 0.001); let cutoff = calculate_cutoff::(128, WindowFunction::Hann); assert_abs_diff_eq!(cutoff, 0.974, epsilon = 0.001); let cutoff = calculate_cutoff::(256, WindowFunction::Hann); assert_abs_diff_eq!(cutoff, 0.987, epsilon = 0.001); let cutoff = calculate_cutoff::(128, WindowFunction::Hann2); assert_abs_diff_eq!(cutoff, 0.958, epsilon = 0.001); let cutoff = calculate_cutoff::(256, WindowFunction::Hann2); assert_abs_diff_eq!(cutoff, 0.979, epsilon = 0.001); } } rubato-0.16.2/utils/cutoff_check_cubic.py000064400000000000000000000047461046102023000165230ustar 00000000000000# Script for viewing the results from the fitting script. # Needs numpy and matplotlib import numpy as np from matplotlib import pyplot as plt import numpy.fft as fft import math from cutoff_fit_cubic import blackman_harris, blackman, hann, pad_vec, make_sinc, FACTOR, SINCLENGTHS, FS windows_bh = [] windows_hann = [] windows_blackman = [] for sinclen in SINCLENGTHS: wind_bh = blackman_harris(sinclen*FACTOR) wind_blackman = blackman(sinclen*FACTOR) wind_hann = hann(sinclen*FACTOR) windows_bh.append(wind_bh) windows_blackman.append(wind_blackman) windows_hann.append(wind_hann) waves = [] mins_bh = [] mins_bh2 = [] mins_blackman = [] mins_blackman2 = [] mins_hann = [] mins_hann2 = [] consts = [ (8.041443677716476, 55.9506779343387, 898.0287985384213), (13.745202940783823, 121.73532586374934, 5964.163279612051), (6.159598046201173, 18.926415097606878, 653.4247430458968), (9.506235102129398, 79.13120634953742, 1502.2316160588925), (3.3481080887677166, 10.106519434875038, 78.96345249024414), (5.38751148378734, 29.69451915489501, 184.82117462266237), ] def calc_cutoff(length, idx): return 1.0 / (consts[idx][0]/length + consts[idx][1]/length**2 + consts[idx][2]/length**3 + 1) def plot_sinc_fft(sinc, fignbr, title): sinc = 2**15*sinc/np.sum(sinc) npoints = len(sinc) divfact = npoints/2 f = np.linspace(0, FACTOR*FS/2.0, math.floor(npoints/2)) valfft = fft.fft(sinc) cut = valfft[0:math.floor(npoints/2)] ampl = 20*np.log10(np.abs(cut)/divfact) plt.figure(fignbr) plt.plot(f, ampl) plt.title(title) plt.legend(SINCLENGTHS) for w_bh, w_bm, w_h in zip(windows_bh, windows_blackman, windows_hann): sinc_len = len(w_bh)/FACTOR sinc = pad_vec(make_sinc(sinc_len, calc_cutoff(sinc_len, 0), FACTOR, 1, w_bh), 2**16) plot_sinc_fft(sinc, 1, "BlackmanHarris") sinc = pad_vec(make_sinc(sinc_len, calc_cutoff(sinc_len, 1), FACTOR, 2, w_bh), 2**16) plot_sinc_fft(sinc, 2, "BlackmanHarris2") sinc = pad_vec(make_sinc(sinc_len, calc_cutoff(sinc_len, 2), FACTOR, 1, w_bm), 2**16) plot_sinc_fft(sinc, 3, "Blackman") sinc = pad_vec(make_sinc(sinc_len, calc_cutoff(sinc_len, 3), FACTOR, 2, w_bm), 2**16) plot_sinc_fft(sinc, 4, "Blackman2") sinc = pad_vec(make_sinc(sinc_len, calc_cutoff(sinc_len, 4), FACTOR, 1, w_h), 2**16) plot_sinc_fft(sinc, 5, "Hann") sinc = pad_vec(make_sinc(sinc_len, calc_cutoff(sinc_len, 5), FACTOR, 2, w_h), 2**16) plot_sinc_fft(sinc, 6, "Hann2") plt.show() rubato-0.16.2/utils/cutoff_fit_cubic.py000064400000000000000000000124201046102023000162140ustar 00000000000000# Script for fitting the relative cutoff as function of sinc length, for the various window functions. # Needs numpy, scipy and matplotlib import numpy as np from scipy.signal import find_peaks from scipy.optimize import minimize, curve_fit from matplotlib import pyplot as plt import numpy.fft as fft import math def blackman_harris(npoints): x=np.arange(0,npoints) y= 0.35875 - 0.48829*np.cos(2*np.pi*x/npoints) + 0.14128*np.cos(4*np.pi*x/npoints) - 0.01168*np.cos(6*np.pi*x/npoints) return y def blackman(npoints): x=np.arange(0,npoints) y= 0.42 - 0.5*np.cos(2*np.pi*x/npoints) + 0.08*np.cos(4*np.pi*x/npoints) return y def sine(npoints): x=np.arange(0,npoints) y= np.sin(np.pi*x/npoints) return y def raised_cosine(npoints, a0): x=np.arange(0,npoints) y= a0 - (1-a0)*np.cos(2*np.pi*x/npoints) return y def hann(npoints): a0=0.5 return raised_cosine(npoints, a0) def rect(npoints): a0=1.0 return raised_cosine(npoints, a0) def hamming(npoints): a0=0.53836 return raised_cosine(npoints, a0) def make_sinc(npoints, cutoff, factor, power, window): totpoints = npoints*factor x=np.arange(-totpoints/2, totpoints/2) y=np.sinc(x*cutoff/factor) for n in range(power): y=y*window return y def pad_vec(vec, npoints): ynew = np.zeros(npoints) ynew[0:len(vec)] = vec return ynew FACTOR = 10 SINCLENGTHS = [32, 48, 64, 96, 128, 160, 256, 320, 512, 768, 1024, 1536, 2048] FS=20000 windows_bh = [] windows_hann = [] windows_blackman = [] labels = [] windows = {"BlackmanHarris": [], "Blackman": [], "Hann": []} for sinclen in SINCLENGTHS: wind_bh = blackman_harris(sinclen*FACTOR) wind_blackman = blackman(sinclen*FACTOR) wind_hann = hann(sinclen*FACTOR) windows["BlackmanHarris"].append(wind_bh) windows["Blackman"].append(wind_blackman) windows["Hann"].append(wind_hann) waves = [] mins = {} def get_first_min(sinc): npoints = len(sinc) divfact = npoints/2 f = np.linspace(0, FACTOR*FS/2.0, math.floor(npoints/2)) valfft = fft.fft(sinc) cut = valfft[0:math.floor(npoints/2)] ampl = 20*np.log10(np.abs(cut)/divfact) minima, _ = find_peaks(-ampl, height=100) #print(minima[0]) #plt.figure(10) #plt.plot(f, ampl, f[minima[0]], ampl[minima[0]], '*') #print("min at", f[minima[0]]) return f[minima[0]] def plot_sinc_fft(cutoff, window, power): sinc_len = len(window)/FACTOR sinc = pad_vec(make_sinc(sinc_len, cutoff, FACTOR, power, window), 2**16) divfact = sinc_len/2 f = np.linspace(0, FACTOR*FS/2.0, math.floor(len(sinc)/2)) valfft = fft.fft(sinc) cut = valfft[0:math.floor(len(sinc)/2)] ampl = 20*np.log10(np.abs(cut)/divfact) minima, _ = find_peaks(-ampl, height=100) plt.figure() plt.plot(f, ampl) plt.axvline(x = f[minima[0]]) if __name__ == "__main__": cutoffs = {"BlackmanHarris": [[], []], "Blackman": [[], []], "Hann": [[], []]} # Fit the cutoff frequency to place the first minimum at the desired frequency. for name, winds in windows.items(): for power in range(2): for wind in winds: def get_offset(cutoff): sinc_len = len(wind)/FACTOR sinc = pad_vec(make_sinc(sinc_len, cutoff, FACTOR, power+1, wind), 2**16) diff = get_first_min(sinc) - FS/2 return abs(diff) res = minimize(get_offset, [1.0], method='Nelder-Mead', tol=1e-7) cutoffs[name][power].append(res.x[0]) #plot_sinc_fft(res.x[0], wind, power+1) def func(x, a, b, c): return 1/(a/x + b/x**2 +c/x**3 + 1) fignbr = 100 constants = {"BlackmanHarris": [], "Blackman": [], "Hann": []} for name, powers in cutoffs.items(): for power, values in enumerate(powers): popt, pcov = curve_fit(func, SINCLENGTHS, values) constants[name].append(popt[0:3]) fitted = [func(l, popt[0], popt[1], popt[2]) for l in SINCLENGTHS] residuals = [f - d for f, d in zip(fitted, values)] fig, axs = plt.subplots(2, num=fignbr) axs[0].plot(SINCLENGTHS, values, '*', SINCLENGTHS, fitted, '-') axs[0].set_title(f"{name}, power {power+1}") axs[0].set_xlabel("sinc length") axs[0].set_ylabel("relative cutoff") axs[1].plot(SINCLENGTHS, residuals) axs[1].set_title(f"{name}, power {power+1}") axs[1].set_xlabel("sinc length") axs[1].set_ylabel("fit residual") fignbr += 1 print("\nFitting results:") for name, values in constants.items(): print(name) for power in range(2): print(f"{power+1}: {values[power]}") print("\nCopy to check script:") print("consts = [") for name, values in constants.items(): for power in range(2): vals = [str(v) for v in values[power]] print(f" ({', '.join(vals)}),") print("]") print("") print("\nCopy to windows.rs:") for name, values in constants.items(): for power in range(2): print(f" WindowFunction::{name}{power + 1 if power>0 else ''} => (") for val in values[power]: print(f" T::coerce({val}),") print(" ),") plt.show()