]> prime8.dev >> repos - janitor.git/commitdiff
batman
authorDamian Myrda <monkey.damianek@gmail.com>
Tue, 20 Aug 2024 01:20:15 +0000 (20:20 -0500)
committerDamian Myrda <monkey.damianek@gmail.com>
Tue, 20 Aug 2024 01:20:15 +0000 (20:20 -0500)
125 files changed:
README.md [new file with mode: 0644]
cli/.gitignore [new file with mode: 0644]
cli/Cargo.lock [new file with mode: 0644]
cli/Cargo.toml [new file with mode: 0644]
cli/README.md [new file with mode: 0644]
cli/fbank/.gitignore [new file with mode: 0644]
cli/fbank/.gitmodules [new file with mode: 0644]
cli/fbank/BUILDING.md [new file with mode: 0644]
cli/fbank/Cargo.lock [new file with mode: 0644]
cli/fbank/Cargo.toml [new file with mode: 0644]
cli/fbank/README.md [new file with mode: 0644]
cli/fbank/src/lib.rs [new file with mode: 0644]
cli/fbank/sys/Cargo.lock [new file with mode: 0644]
cli/fbank/sys/Cargo.toml [new file with mode: 0644]
cli/fbank/sys/build.rs [new file with mode: 0644]
cli/fbank/sys/knf/.clang-format [new file with mode: 0644]
cli/fbank/sys/knf/.github/scripts/filter.py [new file with mode: 0644]
cli/fbank/sys/knf/.github/scripts/filter.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.github/workflows/build-wheels-aarch64.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.github/workflows/build-wheels-macos-arm64.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.github/workflows/build-wheels-macos-x64.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.github/workflows/build-wheels-win32.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.github/workflows/build-wheels-win64.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.github/workflows/build-wheels.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.github/workflows/iwyu.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.github/workflows/linux-macos.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.github/workflows/test-wheel.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.github/workflows/windows-x64.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.github/workflows/windows-x86.yaml [new file with mode: 0644]
cli/fbank/sys/knf/.gitignore [new file with mode: 0644]
cli/fbank/sys/knf/CMakeLists.txt [new file with mode: 0644]
cli/fbank/sys/knf/LICENSE [new file with mode: 0644]
cli/fbank/sys/knf/MANIFEST.in [new file with mode: 0644]
cli/fbank/sys/knf/README.md [new file with mode: 0644]
cli/fbank/sys/knf/build-arm-linux-gnueabihf.sh [new file with mode: 0755]
cli/fbank/sys/knf/cmake/Modules/FetchContent.cmake [new file with mode: 0644]
cli/fbank/sys/knf/cmake/Modules/FetchContent/CMakeLists.cmake.in [new file with mode: 0644]
cli/fbank/sys/knf/cmake/Modules/README.md [new file with mode: 0644]
cli/fbank/sys/knf/cmake/__init__.py [new file with mode: 0644]
cli/fbank/sys/knf/cmake/cmake_extension.py [new file with mode: 0644]
cli/fbank/sys/knf/cmake/googletest.cmake [new file with mode: 0644]
cli/fbank/sys/knf/cmake/pybind11.cmake [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/CMakeLists.txt [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/CMakeLists.txt [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/CPPLINT.cfg [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-fbank.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-fbank.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-functions.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-functions.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-mfcc.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-mfcc.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-window.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-window.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/fftsg.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/kaldi-math.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/kaldi-math.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/log.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/log.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/mel-computations.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/mel-computations.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/online-feature.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/online-feature.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/rfft.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/rfft.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-log.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-online-fbank.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-online-feature.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-rfft.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/whisper-feature.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/csrc/whisper-feature.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/CMakeLists.txt [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/CMakeLists.txt [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-fbank.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-fbank.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-mfcc.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-mfcc.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-window.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-window.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/kaldi-native-fbank.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/kaldi-native-fbank.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/mel-computations.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/mel-computations.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/online-feature.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/online-feature.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/rfft.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/rfft.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/utils.cc [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/utils.h [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/kaldi_native_fbank/__init__.py [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/tests/CMakeLists.txt [new file with mode: 0644]
cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_fbank_options.py [new file with mode: 0755]
cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_feature_window_function.py [new file with mode: 0755]
cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_frame_extraction_options.py [new file with mode: 0755]
cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_mel_bank_options.py [new file with mode: 0755]
cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_online_fbank.py [new file with mode: 0755]
cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_online_mfcc.py [new file with mode: 0755]
cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_online_whisper_fbank.py [new file with mode: 0755]
cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_rfft.py [new file with mode: 0755]
cli/fbank/sys/knf/scripts/check_style_cpplint.sh [new file with mode: 0755]
cli/fbank/sys/knf/scripts/utils.sh [new file with mode: 0644]
cli/fbank/sys/knf/setup.py [new file with mode: 0644]
cli/fbank/sys/knf/toolchains/README.md [new file with mode: 0644]
cli/fbank/sys/knf/toolchains/arm-linux-gnueabihf.toolchain.cmake [new file with mode: 0644]
cli/fbank/sys/knfc/CMakeLists.txt [new file with mode: 0644]
cli/fbank/sys/knfc/knfc.cc [new file with mode: 0644]
cli/fbank/sys/knfc/knfc.h [new file with mode: 0644]
cli/fbank/sys/src/lib.rs [new file with mode: 0644]
cli/fbank/sys/wrapper.hpp [new file with mode: 0644]
cli/src/audio.rs [new file with mode: 0644]
cli/src/main.rs [new file with mode: 0644]
cli/src/processing.rs [new file with mode: 0644]
service/.gitignore [new file with mode: 0644]
service/Cargo.lock [new file with mode: 0644]
service/Cargo.toml [new file with mode: 0644]
service/README.md [new file with mode: 0644]
service/ast/.gitignore [new file with mode: 0644]
service/ast/README.md [new file with mode: 0644]
service/ast/create.py [new file with mode: 0644]
service/ast/environment.yml [new file with mode: 0644]
service/ast/model.py [new file with mode: 0644]
service/build_model.sh [new file with mode: 0755]
service/src/main.rs [new file with mode: 0644]
service/src/model.rs [new file with mode: 0644]
service/src/queue.rs [new file with mode: 0644]
service/src/tensor.rs [new file with mode: 0644]

diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..482ad73
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# Janitor
+
+A tool for labelling files into three categories: speech, music, and noise.
diff --git a/cli/.gitignore b/cli/.gitignore
new file mode 100644 (file)
index 0000000..b83d222
--- /dev/null
@@ -0,0 +1 @@
+/target/
diff --git a/cli/Cargo.lock b/cli/Cargo.lock
new file mode 100644 (file)
index 0000000..6cd5bee
--- /dev/null
@@ -0,0 +1,2517 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alsa"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37fe60779335388a88c01ac6c3be40304d1e349de3ada3b15f7808bb90fa9dce"
+dependencies = [
+ "alsa-sys",
+ "bitflags 2.6.0",
+ "libc",
+]
+
+[[package]]
+name = "alsa-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "async-channel"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-fs"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
+dependencies = [
+ "async-lock",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
+[[package]]
+name = "async-walkdir"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20235b6899dd1cb74a9afac0abf5b4a20c0e500dd6537280f4096e1b9f14da20"
+dependencies = [
+ "async-fs",
+ "futures-lite",
+ "thiserror",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "backtrace"
+version = "0.3.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bindgen"
+version = "0.69.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
+dependencies = [
+ "bitflags 2.6.0",
+ "cexpr",
+ "clang-sys",
+ "itertools 0.12.1",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "blocking"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "futures-io",
+ "futures-lite",
+ "piper",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "byte-slice-cast"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c"
+
+[[package]]
+name = "bytemuck"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
+
+[[package]]
+name = "cc"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
+dependencies = [
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
+
+[[package]]
+name = "claxon"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688"
+
+[[package]]
+name = "cmake"
+version = "0.1.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
+
+[[package]]
+name = "colored"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
+dependencies = [
+ "lazy_static",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "coreaudio-rs"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation-sys",
+ "coreaudio-sys",
+]
+
+[[package]]
+name = "coreaudio-sys"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f01585027057ff5f0a5bf276174ae4c1594a2c5bde93d5f46a016d76270f5a9"
+dependencies = [
+ "bindgen",
+]
+
+[[package]]
+name = "cpal"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779"
+dependencies = [
+ "alsa",
+ "core-foundation-sys",
+ "coreaudio-rs",
+ "dasp_sample",
+ "jni",
+ "js-sys",
+ "libc",
+ "mach2",
+ "ndk",
+ "ndk-context",
+ "oboe",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+
+[[package]]
+name = "dasp_sample"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
+
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
+dependencies = [
+ "event-listener",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "eyre"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
+dependencies = [
+ "indenter",
+ "once_cell",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "fon"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad46a0e6c9bc688823a742aa969b5c08fdc56c2a436ee00d5c6fbcb5982c55c4"
+dependencies = [
+ "libm",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-lite"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+]
+
+[[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 = "gimli"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "h2"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "hound"
+version = "3.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
+
+[[package]]
+name = "http"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
+
+[[package]]
+name = "hyper"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
+dependencies = [
+ "futures-util",
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indenter"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
+
+[[package]]
+name = "indexmap"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+
+[[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.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "janitor-cli"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-walkdir",
+ "byte-slice-cast",
+ "clap",
+ "fon",
+ "itertools 0.13.0",
+ "knf-rs",
+ "mime_guess",
+ "reqwest",
+ "rodio",
+ "safetensors",
+ "serde",
+ "serde_json",
+ "spinoff",
+ "tokio",
+ "tokio-stream",
+]
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "jobserver"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "knf-rs"
+version = "0.2.4"
+dependencies = [
+ "eyre",
+ "knf-rs-sys",
+]
+
+[[package]]
+name = "knf-rs-sys"
+version = "0.2.4"
+dependencies = [
+ "bindgen",
+ "cmake",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "lewton"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030"
+dependencies = [
+ "byteorder",
+ "ogg",
+ "tinyvec",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.158"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
+
+[[package]]
+name = "libloading"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
+dependencies = [
+ "cfg-if",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "libm"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "mach2"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "wasi",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "ndk"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
+dependencies = [
+ "bitflags 2.6.0",
+ "jni-sys",
+ "log",
+ "ndk-sys",
+ "num_enum",
+ "thiserror",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "ndk-sys"
+version = "0.5.0+25.2.9519653"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "object"
+version = "0.36.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "oboe"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
+dependencies = [
+ "jni",
+ "ndk",
+ "ndk-context",
+ "num-derive",
+ "num-traits",
+ "oboe-sys",
+]
+
+[[package]]
+name = "oboe-sys"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ogg"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "openssl"
+version = "0.10.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
+dependencies = [
+ "bitflags 2.6.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "piper"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
+dependencies = [
+ "bitflags 2.6.0",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
+[[package]]
+name = "reqwest"
+version = "0.12.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-tls",
+ "hyper-util",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "mime_guess",
+ "native-tls",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pemfile",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "system-configuration",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows-registry",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rodio"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6006a627c1a38d37f3d3a85c6575418cfe34a5392d60a686d0071e1c8d427acb"
+dependencies = [
+ "claxon",
+ "cpal",
+ "hound",
+ "lewton",
+ "symphonia",
+ "thiserror",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags 2.6.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
+dependencies = [
+ "once_cell",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
+dependencies = [
+ "base64",
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "safetensors"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7725d4d98fa515472f43a6e2bbf956c48e06b89bb50593a040e5945160214450"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[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 = "schannel"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.6.0",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.208"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.208"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "socket2"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "spinoff"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20aa2ed67fbb202e7b716ff8bfc6571dd9301617767380197d701c31124e88f6"
+dependencies = [
+ "colored",
+ "once_cell",
+ "paste",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "symphonia"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9"
+dependencies = [
+ "lazy_static",
+ "symphonia-bundle-mp3",
+ "symphonia-core",
+ "symphonia-metadata",
+]
+
+[[package]]
+name = "symphonia-bundle-mp3"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4"
+dependencies = [
+ "lazy_static",
+ "log",
+ "symphonia-core",
+ "symphonia-metadata",
+]
+
+[[package]]
+name = "symphonia-core"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3"
+dependencies = [
+ "arrayvec",
+ "bitflags 1.3.2",
+ "bytemuck",
+ "lazy_static",
+ "log",
+]
+
+[[package]]
+name = "symphonia-metadata"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c"
+dependencies = [
+ "encoding_rs",
+ "lazy_static",
+ "log",
+ "symphonia-core",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "658bc6ee10a9b4fcf576e9b0819d95ec16f4d2c02d39fd83ac1c8789785c4a42"
+dependencies = [
+ "bitflags 2.6.0",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.39.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
+dependencies = [
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+
+[[package]]
+name = "toml_edit"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project",
+ "pin-project-lite",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "unicase"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[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 = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+
+[[package]]
+name = "web-sys"
+version = "0.3.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "windows"
+version = "0.54.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.54.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
+dependencies = [
+ "windows-result 0.1.2",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-registry"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
+dependencies = [
+ "windows-result 0.2.0",
+ "windows-strings",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
+dependencies = [
+ "windows-result 0.2.0",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[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.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[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.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[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.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[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.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[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.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[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.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
new file mode 100644 (file)
index 0000000..e431616
--- /dev/null
@@ -0,0 +1,22 @@
+[package]
+name = "janitor-cli"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.86"
+tokio = { version = "1.38.0", features = ["full"] }
+tokio-stream = "0.1.15"
+itertools = "0.13.0"
+clap = { version = "4.5.8", features = ["derive"] }
+spinoff = "0.8.0"
+async-walkdir = "2.0.0"
+mime_guess = "2.0.5"
+rodio = "0.19.0"
+fon = "0.6.0"
+knf-rs = { path = "./fbank/" }
+byte-slice-cast = "1.2.2"
+safetensors = "0.4.4"
+reqwest = { version = "0.12.5", features = ["multipart", "json"] }
+serde = { version = "1.0.204", features = ["derive"] }
+serde_json = "1.0.122"
diff --git a/cli/README.md b/cli/README.md
new file mode 100644 (file)
index 0000000..9fd6a2c
--- /dev/null
@@ -0,0 +1,7 @@
+# Janitor CLI
+
+Reads, parses, and sends data to the janitor service for labelling.
+
+## running
+
+Use `cargo run -- --help` to see all the arguments.
diff --git a/cli/fbank/.gitignore b/cli/fbank/.gitignore
new file mode 100644 (file)
index 0000000..8647666
--- /dev/null
@@ -0,0 +1,2 @@
+target/
+build/
\ No newline at end of file
diff --git a/cli/fbank/.gitmodules b/cli/fbank/.gitmodules
new file mode 100644 (file)
index 0000000..59a2219
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "sys/knf"]
+       path = sys/knf
+       url = https://github.com/csukuangfj/kaldi-native-fbank
diff --git a/cli/fbank/BUILDING.md b/cli/fbank/BUILDING.md
new file mode 100644 (file)
index 0000000..aef4cdc
--- /dev/null
@@ -0,0 +1,20 @@
+# Building
+
+_Prepare knf_
+
+```console
+git clone https://github.com/thewh1teagle/knf-rs --recursive
+```
+
+_Build_
+
+```console
+cargo build
+```
+
+_Build knf_
+
+```console
+cmake -B build .  -DKALDI_NATIVE_FBANK_BUILD_PYTHON=OFF -DKALDI_NATIVE_FBANK_BUILD_TESTS=OFF
+cmake --build build --config Release
+```
diff --git a/cli/fbank/Cargo.lock b/cli/fbank/Cargo.lock
new file mode 100644 (file)
index 0000000..a687cb9
--- /dev/null
@@ -0,0 +1,406 @@
+# 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 = "bindgen"
+version = "0.69.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "cc"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549"
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "eyre"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
+dependencies = [
+ "indenter",
+ "once_cell",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "indenter"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "knf-rs"
+version = "0.2.4"
+dependencies = [
+ "eyre",
+ "knf-rs-sys",
+]
+
+[[package]]
+name = "knf-rs-sys"
+version = "0.2.4"
+dependencies = [
+ "bindgen",
+ "cmake",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "libloading"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
+dependencies = [
+ "cfg-if",
+ "windows-targets",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "syn"
+version = "2.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+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"
diff --git a/cli/fbank/Cargo.toml b/cli/fbank/Cargo.toml
new file mode 100644 (file)
index 0000000..abe91cd
--- /dev/null
@@ -0,0 +1,13 @@
+[package]
+name = "knf-rs"
+version = "0.2.4"
+edition = "2021"
+license = "MIT"
+description = "fbank features extractor without external dependencies"
+
+[dependencies]
+knf-rs-sys = { path = "sys", version = "0.2.4", features = [] }
+eyre = "0.6.12"
+
+[features]
+default = []
diff --git a/cli/fbank/README.md b/cli/fbank/README.md
new file mode 100644 (file)
index 0000000..be813fd
--- /dev/null
@@ -0,0 +1,7 @@
+# knf-rs
+
+Forked from https://github.com/thewh1teagle/knf-rs
+
+_fbank features extractor without external dependencies_
+
+Rust bindings to [kaldi-native-fbank](https://github.com/csukuangfj/kaldi-native-fbank)
diff --git a/cli/fbank/src/lib.rs b/cli/fbank/src/lib.rs
new file mode 100644 (file)
index 0000000..901d069
--- /dev/null
@@ -0,0 +1,69 @@
+use eyre::{bail, Context, Result};
+
+const NUM_MEL_BINS: usize = 128;
+
+pub fn compute_fbank(samples: &[f32]) -> Result<Vec<[f32; NUM_MEL_BINS]>> {
+    if samples.is_empty() {
+        bail!("The samples array is empty. No features to compute.")
+    }
+
+    let mut result = unsafe {
+        knf_rs_sys::ComputeFbank(
+            samples.as_ptr(),
+            samples.len().try_into().context("samples len")?,
+        )
+    };
+
+    let frames = unsafe {
+        std::slice::from_raw_parts(
+            result.frames,
+            (result.num_frames * NUM_MEL_BINS as i32) as usize,
+        )
+        .to_vec()
+    };
+
+    let features = unsafe {
+        Box::from_raw(frames.as_ptr() as *mut [[f32; NUM_MEL_BINS]; 0])
+            .to_vec()
+    };
+
+    unsafe {
+        knf_rs_sys::DestroyFbankResult(&mut result as *mut _);
+    }
+
+    Ok(features)
+}
+
+pub fn convert_integer_to_float_audio(samples: &[i16], output: &mut [f32]) {
+    for (input, output) in samples.iter().zip(output.iter_mut()) {
+        *output = *input as f32 / 32768.0;
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::compute_fbank;
+    use std::f32::consts::PI;
+
+    fn generate_sine_wave(sample_rate: usize, duration: usize, frequency: f32) -> Vec<f32> {
+        let waveform_size = sample_rate * duration;
+        let mut waveform = Vec::with_capacity(waveform_size);
+
+        for i in 0..waveform_size {
+            let sample = 0.5 * (2.0 * PI * frequency * i as f32 / sample_rate as f32).sin();
+            waveform.push(sample);
+        }
+        waveform
+    }
+
+    #[test]
+    fn it_works() {
+        let sample_rate = 16000;
+        let duration = 1; // 1 second
+        let frequency = 440.0; // A4 note
+
+        let waveform = generate_sine_wave(sample_rate, duration, frequency);
+        let features = compute_fbank(&waveform);
+        println!("features: {:?}", features);
+    }
+}
diff --git a/cli/fbank/sys/Cargo.lock b/cli/fbank/sys/Cargo.lock
new file mode 100644 (file)
index 0000000..75a16a3
--- /dev/null
@@ -0,0 +1,382 @@
+# 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 = "bindgen"
+version = "0.69.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "cc"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc"
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "knf-rs-sys"
+version = "0.2.2"
+dependencies = [
+ "bindgen",
+ "cmake",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.155"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "libloading"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
+dependencies = [
+ "cfg-if",
+ "windows-targets",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "syn"
+version = "2.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+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"
diff --git a/cli/fbank/sys/Cargo.toml b/cli/fbank/sys/Cargo.toml
new file mode 100644 (file)
index 0000000..6d5e6bc
--- /dev/null
@@ -0,0 +1,25 @@
+[package]
+name = "knf-rs-sys"
+version = "0.2.4"
+edition = "2021"
+license = "MIT"
+description = "fbank features extractor without external dependencies"
+
+include = [
+    "knfc",
+    "knf/kaldi-native-fbank/csrc",
+    "knf/kaldi-native-fbank/CMakeLists.txt",
+    "knf/cmake",
+    "knf/CMakeLists.txt",
+    "knf/LICENSE",
+    "src/*.rs",
+    "build.rs",
+    "wrapper.hpp",
+]
+
+[build-dependencies]
+bindgen = "0.69.4"
+cmake = "0.1.50"
+
+[features]
+default = []
diff --git a/cli/fbank/sys/build.rs b/cli/fbank/sys/build.rs
new file mode 100644 (file)
index 0000000..e6b0937
--- /dev/null
@@ -0,0 +1,110 @@
+use cmake::Config;
+use std::env;
+use std::path::{Path, PathBuf};
+
+macro_rules! debug_log {
+    ($($arg:tt)*) => {
+        if std::env::var("BUILD_DEBUG").is_ok() {
+            println!("cargo:warning=[DEBUG] {}", format!($($arg)*));
+        }
+    };
+}
+
+fn copy_folder(src: &Path, dst: &Path) {
+    std::fs::create_dir_all(dst).expect("Failed to create dst directory");
+    if cfg!(unix) {
+        std::process::Command::new("cp")
+            .arg("-rf")
+            .arg(src)
+            .arg(dst.parent().expect("no parent"))
+            .status()
+            .expect("Failed to execute cp command");
+    }
+
+    if cfg!(windows) {
+        std::process::Command::new("robocopy.exe")
+            .arg("/e")
+            .arg(src)
+            .arg(dst)
+            .status()
+            .expect("Failed to execute robocopy command");
+    }
+}
+
+fn main() {
+    let target = env::var("TARGET").expect("no target");
+    let out_dir = PathBuf::from(env::var("OUT_DIR").expect("no out dir"));
+    let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("Failed to get CARGO_MANIFEST_DIR");
+    let knf_src = Path::new(&manifest_dir).join("knf");
+    let knf_dst = out_dir.join("knf");
+    let knfc_src = Path::new(&manifest_dir).join("knfc");
+    let knfc_dst = out_dir.join("knfc");
+    let static_crt = env::var("KNF_STATIC_CRT").map(|v| v == "1").unwrap_or(false);
+
+    let profile = if cfg!(debug_assertions) {
+        "Debug"
+    } else {
+        "Release"
+    };
+
+    debug_log!("TARGET: {}", target);
+    debug_log!("CARGO_MANIFEST_DIR: {}", manifest_dir);
+    debug_log!("OUT_DIR: {}", out_dir.display());
+
+    if !knf_dst.exists() {
+        debug_log!("Copy {} to {}", knf_src.display(), knf_dst.display());
+        copy_folder(&knf_src, &knf_dst);
+    }
+
+    if !knfc_dst.exists() {
+        debug_log!("Copy {} to {}", knfc_src.display(), knfc_dst.display());
+        copy_folder(&knfc_src, &knfc_dst);
+    }
+
+    // Bindings
+    let bindings = bindgen::Builder::default()
+        .header("wrapper.hpp")
+        .clang_arg(format!("-I{}", knfc_dst.display()))
+        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
+        .generate()
+        .expect("Failed to generate bindings");
+
+    // Write the generated bindings to an output file
+    let bindings_path = out_dir.join("bindings.rs");
+    bindings
+        .write_to_file(bindings_path)
+        .expect("Failed to write bindings");
+
+    println!("cargo:rerun-if-changed=./knf");
+    println!("cargo:rerun-if-changed=./knfc");
+    println!("cargo:rerun-if-changed=wrapper.h");
+
+    debug_log!("Bindings Created");
+
+    let mut config = Config::new(&knfc_dst);
+
+    if cfg!(windows) {
+        config.static_crt(static_crt);
+        debug_log!("STATIC_CRT: {}", static_crt);
+    }
+
+    config
+        .profile(profile)
+        .very_verbose(std::env::var("CMAKE_VERBOSE").is_ok()) // Not verbose by default
+        .always_configure(false)
+        .build();
+
+    println!("cargo:rustc-link-search={}", out_dir.join("lib").display());
+
+    // Link
+    if cfg!(target_os = "macos") {
+        println!("cargo:rustc-link-lib=c++");
+    }
+
+    if cfg!(target_os = "linux") {
+        println!("cargo:rustc-link-lib=stdc++");
+    }
+
+    println!("cargo:rustc-link-lib=static={}", "knfc");
+    println!("cargo:rustc-link-lib=static={}", "kaldi-native-fbank-core");
+}
diff --git a/cli/fbank/sys/knf/.clang-format b/cli/fbank/sys/knf/.clang-format
new file mode 100644 (file)
index 0000000..c65e772
--- /dev/null
@@ -0,0 +1,9 @@
+---
+BasedOnStyle: Google
+---
+Language:               Cpp
+Cpp11BracedListStyle:   true
+Standard:               Cpp11
+DerivePointerAlignment: false
+PointerAlignment:       Right
+---
diff --git a/cli/fbank/sys/knf/.github/scripts/filter.py b/cli/fbank/sys/knf/.github/scripts/filter.py
new file mode 100644 (file)
index 0000000..7c36e64
--- /dev/null
@@ -0,0 +1,85 @@
+# This file is from
+# https://github.com/acts-project/acts/blob/main/CI/iwyu/filter.py
+import sys
+import yaml
+import re
+import argparse
+from collections import namedtuple
+
+
+Config = namedtuple(
+    "Config", ["remove_lines", "replace_lines", "ignore_files"], defaults=[[], [], []]
+)
+
+
+class State:
+    skip_file: bool = False
+
+
+def parse_config(config: Config):
+    remove_lines = []
+    for s in config["remove_lines"]:
+        remove_lines.append(re.compile(s))
+
+    replace_lines = []
+    for s in config["replace_lines"]:
+        s, r = list(s.items())[0]
+        replace_lines.append((re.compile(s), r))
+
+    ignore_files = []
+    for s in config["ignore_files"]:
+        ignore_files.append(re.compile(s))
+
+    return Config(
+        remove_lines=remove_lines,
+        replace_lines=replace_lines,
+        ignore_files=ignore_files,
+    )
+
+
+def filter(line: str, config: Config, state: State):
+    if state.skip_file:
+        if line.endswith("---\n"):
+            state.skip_file = False
+        return None
+
+    if line.endswith(" should add these lines:\n"):
+        for s in config.ignore_files:
+            if s.search(line):
+                state.skip_file = True
+                return None
+
+    for s in config.remove_lines:
+        if s.search(line):
+            return None
+
+    for s, r in config.replace_lines:
+        if s.search(line):
+            return s.sub(r, line)
+
+    return line
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("config")
+parser.add_argument("input")
+parser.add_argument("output")
+args = parser.parse_args()
+
+with open(args.config, "r") as config_file:
+    try:
+        config = yaml.safe_load(config_file)
+        config = parse_config(config)
+    except yaml.YAMLError as exc:
+        print(exc)
+        sys.exit(1)
+
+with open(args.input, "r") as input_file, open(args.output, "w") as output_file:
+    state = State()
+
+    for line in input_file:
+        filtered_line = filter(line, config, state)
+        if filtered_line is not None:
+            output_file.write(filtered_line)
+
+sys.exit(0)
diff --git a/cli/fbank/sys/knf/.github/scripts/filter.yaml b/cli/fbank/sys/knf/.github/scripts/filter.yaml
new file mode 100644 (file)
index 0000000..23578cb
--- /dev/null
@@ -0,0 +1,23 @@
+# This file is from
+# https://github.com/acts-project/acts/blob/main/CI/iwyu/filter.yaml
+remove_lines:
+  # ignore pybind11
+  - "^(- )?#include <pybind11/"
+  - "^(- )?#include [<\"]pybind11/"
+  # ignore python
+  - "^#include <abstract.h>"
+  - "^#include <floatobject.h>"
+  - "^#include <longobject.h>"
+  - "^#include <pyerrors.h>"
+
+replace_lines:
+  - "^#include <assert\\.h>": "#include <cassert>"
+  - "^#include <stddef\\.h>": "#include <cstddef>"
+  - "^#include <math\\.h>": "#include <cmath>"
+  - "^#include <limits\\.h>": "#include <climits>"
+  - "^#include <unistd\\.h>": "#include <cunistd>"
+  - "^#include <stdint\\.h>": "#include <cstdint>"
+  - "^#include <stdlib.h>": "#include <cstdlib>"
+
+ignore_files:
+  - ".*_deps/"
diff --git a/cli/fbank/sys/knf/.github/workflows/build-wheels-aarch64.yaml b/cli/fbank/sys/knf/.github/workflows/build-wheels-aarch64.yaml
new file mode 100644 (file)
index 0000000..1db0f2e
--- /dev/null
@@ -0,0 +1,61 @@
+name: build-wheels-aarch64
+
+on:
+  push:
+    branches:
+      - wheel
+    tags:
+      - '*'
+
+concurrency:
+  group: build-wheels-aarch64-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  build_wheels_aarch64:
+    name: ${{ matrix.python-version }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest]
+        python-version: ["cp37", "cp38", "cp39", "cp310", "cp311", "cp312"]
+
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Set up QEMU
+        uses: docker/setup-qemu-action@v2
+        with:
+          platforms: all
+
+      # see https://cibuildwheel.readthedocs.io/en/stable/changelog/
+      # for a list of versions
+      - name: Build wheels
+        uses: pypa/cibuildwheel@v2.16.5
+        env:
+          CIBW_BUILD: "${{ matrix.python-version}}-* "
+          CIBW_SKIP: "cp27-* cp35-* cp36-* *-win32 pp* *-musllinux* *-manylinux_i686"
+          CIBW_BUILD_VERBOSITY: 3
+          CIBW_ARCHS_LINUX: aarch64
+
+      - name: Display wheels
+        shell: bash
+        run: |
+          ls -lh ./wheelhouse/
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: wheel-${{ matrix.python-version }}
+          path: ./wheelhouse/*.whl
+
+      - name: Publish wheels to PyPI
+        env:
+          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+        shell: bash
+        run: |
+          python3 -m pip install --upgrade pip
+          python3 -m pip install wheel twine setuptools
+
+          twine upload ./wheelhouse/*.whl
diff --git a/cli/fbank/sys/knf/.github/workflows/build-wheels-macos-arm64.yaml b/cli/fbank/sys/knf/.github/workflows/build-wheels-macos-arm64.yaml
new file mode 100644 (file)
index 0000000..46c76a9
--- /dev/null
@@ -0,0 +1,73 @@
+name: build-wheels-macos-arm64
+
+on:
+  push:
+    branches:
+      - wheel
+    tags:
+      - '*'
+  workflow_dispatch:
+
+concurrency:
+  group: build-wheels-macos-arm64-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  build_wheels_macos_arm64:
+    name: ${{ matrix.python-version }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [macos-latest]
+        python-version: ["cp37", "cp38", "cp39", "cp310", "cp311", "cp312"]
+
+    steps:
+      - uses: actions/checkout@v4
+
+      # see https://cibuildwheel.readthedocs.io/en/stable/changelog/
+      # for a list of versions
+      - name: Build wheels
+        if: matrix.python-version == 'cp37'
+        uses: pypa/cibuildwheel@v2.11.4
+        env:
+          CIBW_BUILD: "${{ matrix.python-version}}-* "
+          CIBW_ENVIRONMENT: KALDI_NATIVE_FBANK_CMAKE_ARGS="-DCMAKE_OSX_ARCHITECTURES='arm64'"
+          CIBW_ARCHS: "arm64"
+          CIBW_BUILD_VERBOSITY: 3
+
+      - name: Build wheels
+        if: matrix.python-version != 'cp37'
+        uses: pypa/cibuildwheel@v2.15.0
+        env:
+          CIBW_BUILD: "${{ matrix.python-version}}-* "
+          CIBW_ENVIRONMENT: KALDI_NATIVE_FBANK_CMAKE_ARGS="-DCMAKE_OSX_ARCHITECTURES='arm64'"
+          CIBW_ARCHS: "arm64"
+          CIBW_BUILD_VERBOSITY: 3
+
+      - name: Display wheels
+        shell: bash
+        run: |
+          ls -lh ./wheelhouse/
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: wheel-${{ matrix.python-version }}
+          path: ./wheelhouse/*.whl
+
+      - name: Publish wheels to PyPI
+        env:
+          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+        shell: bash
+        run: |
+          opts='--break-system-packages'
+          v=${{ matrix.python-version }}
+          if [[ $v == cp37 || $v == cp38 || $v == cp39 ]]; then
+            opts=''
+          fi
+
+          python3 -m pip install $opts --upgrade pip
+          python3 -m pip install $opts wheel twine setuptools
+
+          twine upload ./wheelhouse/*.whl
diff --git a/cli/fbank/sys/knf/.github/workflows/build-wheels-macos-x64.yaml b/cli/fbank/sys/knf/.github/workflows/build-wheels-macos-x64.yaml
new file mode 100644 (file)
index 0000000..a58dad3
--- /dev/null
@@ -0,0 +1,73 @@
+name: build-wheels-macos-x64
+
+on:
+  push:
+    branches:
+      - wheel
+    tags:
+      - '*'
+  workflow_dispatch:
+
+concurrency:
+  group: build-wheels-macos-x64-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  build_wheels_x64:
+    name: ${{ matrix.python-version }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [macos-latest]
+        python-version: ["cp37", "cp38", "cp39", "cp310", "cp311", "cp312"]
+
+    steps:
+      - uses: actions/checkout@v2
+
+      # see https://cibuildwheel.readthedocs.io/en/stable/changelog/
+      # for a list of versions
+      - name: Build wheels
+        if: matrix.python-version == 'cp37'
+        uses: pypa/cibuildwheel@v2.11.4
+        env:
+          CIBW_BUILD: "${{ matrix.python-version}}-* "
+          CIBW_ENVIRONMENT: KALDI_NATIVE_FBANK_CMAKE_ARGS="-DCMAKE_OSX_ARCHITECTURES='x86_64'"
+          CIBW_ARCHS: "x86_64"
+          CIBW_BUILD_VERBOSITY: 3
+
+      - name: Build wheels
+        if: matrix.python-version != 'cp37'
+        uses: pypa/cibuildwheel@v2.15.0
+        env:
+          CIBW_BUILD: "${{ matrix.python-version}}-* "
+          CIBW_ENVIRONMENT: KALDI_NATIVE_FBANK_CMAKE_ARGS="-DCMAKE_OSX_ARCHITECTURES='x86_64'"
+          CIBW_ARCHS: "x86_64"
+          CIBW_BUILD_VERBOSITY: 3
+
+      - name: Display wheels
+        shell: bash
+        run: |
+          ls -lh ./wheelhouse/
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: wheel-${{ matrix.python-version }}
+          path: ./wheelhouse/*.whl
+
+      - name: Publish wheels to PyPI
+        env:
+          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+        shell: bash
+        run: |
+          opts='--break-system-packages'
+          v=${{ matrix.python-version }}
+          if [[ $v == cp37 || $v == cp38 || $v == cp39 ]]; then
+            opts=''
+          fi
+
+          python3 -m pip install $opts --upgrade pip
+          python3 -m pip install $opts wheel twine setuptools
+
+          twine upload ./wheelhouse/*.whl
diff --git a/cli/fbank/sys/knf/.github/workflows/build-wheels-win32.yaml b/cli/fbank/sys/knf/.github/workflows/build-wheels-win32.yaml
new file mode 100644 (file)
index 0000000..030294d
--- /dev/null
@@ -0,0 +1,57 @@
+name: build-wheels-win32
+
+on:
+  push:
+    branches:
+      - wheel
+    tags:
+      - '*'
+  workflow_dispatch:
+
+concurrency:
+  group: build-wheels-win32-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  build_wheels:
+    name: ${{ matrix.python-version }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [windows-latest]
+        python-version: ["cp37", "cp38", "cp39", "cp310", "cp311", "cp312"]
+
+    steps:
+      - uses: actions/checkout@v4
+
+      # see https://cibuildwheel.readthedocs.io/en/stable/changelog/
+      # for a list of versions
+      - name: Build wheels
+        uses: pypa/cibuildwheel@v2.16.5
+        env:
+          CIBW_BUILD: "${{ matrix.python-version}}-* "
+          CIBW_ENVIRONMENT: KALDI_NATIVE_FBANK_CMAKE_ARGS="-A Win32"
+          CIBW_SKIP: "*-win_amd64"
+          CIBW_BUILD_VERBOSITY: 3
+
+      - name: Display wheels
+        shell: bash
+        run: |
+          ls -lh ./wheelhouse/
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: wheel-${{ matrix.python-version }}
+          path: ./wheelhouse/*.whl
+
+      - name: Publish wheels to PyPI
+        env:
+          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+        shell: bash
+        run: |
+          python3 -m pip install --upgrade pip
+          python3 -m pip install wheel twine setuptools
+
+          twine upload ./wheelhouse/*.whl
diff --git a/cli/fbank/sys/knf/.github/workflows/build-wheels-win64.yaml b/cli/fbank/sys/knf/.github/workflows/build-wheels-win64.yaml
new file mode 100644 (file)
index 0000000..029345c
--- /dev/null
@@ -0,0 +1,56 @@
+name: build-wheels-win64
+
+on:
+  push:
+    branches:
+      - wheel
+    tags:
+      - '*'
+  workflow_dispatch:
+
+concurrency:
+  group: build-wheels-win64-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  build_wheels:
+    name: ${{ matrix.python-version }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [windows-latest]
+        python-version: ["cp37", "cp38", "cp39", "cp310", "cp311", "cp312"]
+
+    steps:
+      - uses: actions/checkout@v4
+
+      # see https://cibuildwheel.readthedocs.io/en/stable/changelog/
+      # for a list of versions
+      - name: Build wheels
+        uses: pypa/cibuildwheel@v2.16.5
+        env:
+          CIBW_BUILD: "${{ matrix.python-version}}-* "
+          CIBW_SKIP: "cp27-* cp35-* *-win32 pp* *-musllinux*"
+          CIBW_BUILD_VERBOSITY: 3
+
+      - name: Display wheels
+        shell: bash
+        run: |
+          ls -lh ./wheelhouse/
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: wheel-${{ matrix.python-version }}
+          path: ./wheelhouse/*.whl
+
+      - name: Publish wheels to PyPI
+        env:
+          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+        shell: bash
+        run: |
+          python3 -m pip install --upgrade pip
+          python3 -m pip install wheel twine setuptools
+
+          twine upload ./wheelhouse/*.whl
diff --git a/cli/fbank/sys/knf/.github/workflows/build-wheels.yaml b/cli/fbank/sys/knf/.github/workflows/build-wheels.yaml
new file mode 100644 (file)
index 0000000..37844c0
--- /dev/null
@@ -0,0 +1,71 @@
+name: build-wheels
+
+on:
+  push:
+    branches:
+      - wheel
+    tags:
+      - '*'
+  workflow_dispatch:
+
+concurrency:
+  group: build-wheels-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  build_wheels:
+    name: ${{ matrix.python-version }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest]
+        python-version: ["cp37", "cp38", "cp39", "cp310", "cp311", "cp312"]
+
+    steps:
+      - uses: actions/checkout@v4
+
+      # see https://cibuildwheel.readthedocs.io/en/stable/changelog/
+      # for a list of versions
+      - name: Build wheels
+        uses: pypa/cibuildwheel@v2.16.5
+        env:
+          CIBW_BUILD: "${{ matrix.python-version}}-* "
+          CIBW_SKIP: "cp27-* cp35-* cp36-* *-win32 pp* *-musllinux* *-manylinux_i686"
+          CIBW_BUILD_VERBOSITY: 3
+
+      - name: Display wheels
+        shell: bash
+        run: |
+          ls -lh ./wheelhouse/
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: wheel-${{ matrix.python-version }}
+          path: ./wheelhouse/*.whl
+
+      - name: Publish wheels to PyPI
+        env:
+          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+        shell: bash
+        run: |
+          python3 -m pip install --upgrade pip
+          python3 -m pip install wheel twine setuptools
+
+          twine upload ./wheelhouse/*.whl
+
+      - name: Build sdist
+        if: ${{ matrix.python-version == 'cp38' }}
+        shell: bash
+        run: |
+          python3 setup.py sdist
+          ls -l dist/*
+
+      - name: Publish sdist to PyPI
+        if: ${{ matrix.python-version == 'cp38' }}
+        env:
+          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+        run: |
+          twine upload dist/*.tar.gz
diff --git a/cli/fbank/sys/knf/.github/workflows/iwyu.yaml b/cli/fbank/sys/knf/.github/workflows/iwyu.yaml
new file mode 100644 (file)
index 0000000..6cc3d4c
--- /dev/null
@@ -0,0 +1,171 @@
+name: iwyu
+
+on:
+  push:
+    branches:
+      - master
+      - iwyu-2
+  pull_request:
+    branches:
+      - master
+
+  workflow_dispatch:
+
+concurrency:
+  group: iwyu-${{ github.ref }}
+  cancel-in-progress: true
+
+# References
+# https://github.com/official-stockfish/Stockfish/blob/master/.github/workflows/iwyu.yml
+# https://github.com/acts-project/acts/actions/runs/8588671882/workflow
+# https://github.com/acts-project/acts/tree/main/CI/iwyu
+
+jobs:
+  iwyu:
+    if: github.repository_owner == 'csukuangfj' || github.repository_owner == 'k2-fsa'
+    permissions:
+      contents: write  # for stefanzweifel/git-auto-commit-action to push code in repo
+    name: Include what you use
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest]
+
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - name: Download required linux packages
+        shell: bash
+        run: |
+          echo "GITHUB_WORKSPACE: $GITHUB_WORKSPACE"
+          sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main'
+          wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
+          sudo apt update
+          sudo apt install -y libclang-17-dev clang-17 libc++-17-dev
+
+      - name: Display llvm
+        shell: bash
+        run: |
+          ls -lh /usr/lib/*llvm*
+
+          ls -lh /usr/bin/*clang*
+
+      - name: cache-iwyu
+        id: cache-iwyu
+        uses: actions/cache@v4
+        with:
+          path: /tmp/iwyu-install
+          key: iwyu-install-2024-04-13
+
+      - name: Get IWYU source
+        if: steps.cache-iwyu.outputs.cache-hit != 'true'
+        uses: actions/checkout@v4
+        with:
+          repository: include-what-you-use/include-what-you-use
+          ref: clang_17
+          path: iwyu
+
+      - name: Build IWYU
+        if: steps.cache-iwyu.outputs.cache-hit != 'true'
+        shell: bash
+        run: |
+          mkdir iwyu-build iwyu-install
+          cmake -B iwyu-build -S iwyu -DCMAKE_PREFIX_PATH=/usr/lib/llvm-17 -DCMAKE_INSTALL_PREFIX=/tmp/iwyu-install
+          cmake --build iwyu-build --target install
+
+          ls -lh /tmp/iwyu-install
+          tree /tmp/iwyu-install
+
+          /tmp/iwyu-install/bin/include-what-you-use --version
+
+      - name: setup path
+        shell: bash
+        run: |
+          echo "/tmp/iwyu-install/bin"  >> "$GITHUB_PATH"
+
+      - name: display include what you use version
+        shell: bash
+        run: |
+          include-what-you-use --version
+          which include-what-you-use
+
+          include-what-you-use --help
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: iwyu-install
+          path: /tmpiwyu-install/*
+
+      - name: Check
+        shell: bash
+        run: |
+          mkdir build
+          cd build
+
+          cmake \
+            -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
+            ..
+
+      - name: Run iwyu_tool.py
+        shell: bash
+        run: |
+          python3 /tmp/iwyu-install/bin/iwyu_tool.py -p build/ -j2 | tee iwyu-output.txt
+
+      - name: Filter IWYU output
+        shell: bash
+        run: |
+          python3 .github/scripts/filter.py .github/scripts/filter.yaml iwyu-output.txt iwyu-filtered.txt
+
+      - name: Show IWYU output
+        shell: bash
+        run: |
+          cat iwyu-output.txt
+
+      - name: Show filtered IWYU output
+        shell: bash
+        run: |
+          cat iwyu-filtered.txt
+
+      - name: Upload IWYU output
+        uses: actions/upload-artifact@v4
+        with:
+          name: iwyu
+          path: |
+            iwyu-output.txt
+            iwyu-filtered.txt
+
+      - name: Apply iwyu
+        shell: bash
+        run: |
+          git status
+          python3 /tmp/iwyu-install/bin/fix_includes.py < iwyu-filtered.txt
+
+          rm -rf iwyu*
+
+      - name: Show diff
+        shell: bash
+        run: |
+          git status
+
+          git diff
+          git diff > a.diff
+          # Download a.diff
+          # Run
+          #   git apply a.diff
+
+      - name: Upload IWYU output
+        uses: actions/upload-artifact@v4
+        with:
+          name: diff
+          path: |
+            a.diff
+
+      # https://github.com/stefanzweifel/git-auto-commit-action
+      - uses: stefanzweifel/git-auto-commit-action@v5
+        if: false
+        with:
+          file_pattern: '*.h *.cc'
+          commit_message: "apply iwyu changes"
diff --git a/cli/fbank/sys/knf/.github/workflows/linux-macos.yaml b/cli/fbank/sys/knf/.github/workflows/linux-macos.yaml
new file mode 100644 (file)
index 0000000..acde044
--- /dev/null
@@ -0,0 +1,76 @@
+name: linux-macos
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+
+concurrency:
+  group: linux-macos-${{ github.ref }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+jobs:
+  linux_macos:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest, macos-latest]
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Setup Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: "3.8"
+
+      - name: Install Python dependencies
+        shell: bash
+        run: |
+          python3 -m pip install --upgrade pip numpy
+          python3 -m pip install wheel twine setuptools
+          if [[ ${{ matrix.os }} == "ubuntu" ]]; then
+            python3 -m pip install torch==1.13.0+cpu -f https://download.pytorch.org/whl/torch_stable.html
+          else
+            python3 -m pip install torch==1.13.0 -f https://download.pytorch.org/whl/torch_stable.html
+          fi
+
+      - name: Configure Cmake
+        shell: bash
+        run: |
+          mkdir build
+          cd build
+          cmake -D CMAKE_BUILD_TYPE=Release ..
+
+      - name: Build kaldi-native-fbank for ubuntu/macos
+        if: startsWith(matrix.os, 'ubuntu') || startsWith(matrix.os, 'macos')
+        run: |
+          cd build
+          make -j2
+          ctest --output-on-failure
+
+      - name: Run tests for ubuntu/macos
+        if: startsWith(matrix.os, 'ubuntu') || startsWith(matrix.os, 'macos')
+        run: |
+          cd build
+          ctest --output-on-failure
+
+      - name: Build Python
+        shell: bash
+        run: |
+          python3 -m pip install --verbose .
+
+      - name: Display Python package version
+        shell: bash
+        run: |
+          python3 -c "import kaldi_native_fbank; print(kaldi_native_fbank.__version__)"
+          python3 -c "import kaldi_native_fbank; print(kaldi_native_fbank.__file__)"
diff --git a/cli/fbank/sys/knf/.github/workflows/test-wheel.yaml b/cli/fbank/sys/knf/.github/workflows/test-wheel.yaml
new file mode 100644 (file)
index 0000000..f16be22
--- /dev/null
@@ -0,0 +1,57 @@
+name: test-wheel
+
+on:
+  push:
+    branches:
+      - test-wheel
+
+  workflow_dispatch:
+
+  schedule:
+    # minute (0-59)
+    # hour (0-23)
+    # day of the month (1-31)
+    # month (1-12)
+    # day of the week (0-6)
+    # nightly build at 23:50 UTC time every day
+    - cron: "50 23 * * *"
+
+concurrency:
+  group: test-wheel-${{ github.ref }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+
+jobs:
+  test_wheel:
+    runs-on: ${{ matrix.os }}
+    name: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [windows-latest, macos-latest, ubuntu-latest]
+        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Setup Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Install kaldi-native-fbank
+        shell: bash
+        run: |
+          python3 -m pip install --upgrade pip numpy
+          python3 -m pip install kaldi-native-fbank
+
+      - name: Display version
+        shell: bash
+        run: |
+          python3 -c "import kaldi_native_fbank; print(kaldi_native_fbank.__file__)"
+          python3 -c "import kaldi_native_fbank; print(kaldi_native_fbank.__version__)"
diff --git a/cli/fbank/sys/knf/.github/workflows/windows-x64.yaml b/cli/fbank/sys/knf/.github/workflows/windows-x64.yaml
new file mode 100644 (file)
index 0000000..ba25626
--- /dev/null
@@ -0,0 +1,81 @@
+name: windows-x64
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+
+concurrency:
+  group: windows-x64-${{ github.ref }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+jobs:
+  windows_x64:
+    name: Test on Win64
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: true
+      matrix:
+        os: [windows-latest]
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Configure CMake
+        shell: bash
+        run: |
+          mkdir build
+          cd build
+          cmake -D CMAKE_BUILD_TYPE=Release ..
+
+
+      - name: Build kaldi-native-fbank for windows
+        shell: bash
+        run: |
+          cd build
+          cmake --build . --target ALL_BUILD --config Release
+          cat CMakeCache.txt
+
+      - name: Run tests for windows
+        shell: bash
+        run: |
+          cd build
+          ctest --verbose -C Release --output-on-failure -E py
+
+      - name: Setup Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: "3.8"
+
+      - name: Install Python dependencies
+        shell: bash
+        run: |
+          python3 -m pip install --upgrade pip pytest
+          python3 -m pip install wheel twine setuptools
+
+      - name: Build Python
+        shell: bash
+        run: |
+          python3 -m pip install --verbose .
+
+      - name: Display Python package version
+        shell: bash
+        run: |
+          python3 -c "import kaldi_native_fbank; print(kaldi_native_fbank.__version__)"
+          python3 -c "import kaldi_native_fbank; print(kaldi_native_fbank.__file__)"
+
+      - name: Run Python tests
+        shell: bash
+        run: |
+          cd kaldi-native-fbank/python/tests
+          python3 ./test_fbank_options.py
+          python3 ./test_frame_extraction_options.py
+          python3 ./test_mel_bank_options.py
diff --git a/cli/fbank/sys/knf/.github/workflows/windows-x86.yaml b/cli/fbank/sys/knf/.github/workflows/windows-x86.yaml
new file mode 100644 (file)
index 0000000..2808702
--- /dev/null
@@ -0,0 +1,67 @@
+name: windows-x86
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+
+concurrency:
+  group: windows-x86-${{ github.ref }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+jobs:
+  windows_x86:
+    runs-on: ${{ matrix.os }}
+    name: ${{ matrix.vs-version }}
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - vs-version: vs2015
+            toolset-version: v140
+            os: windows-2019
+
+          - vs-version: vs2017
+            toolset-version: v141
+            os: windows-2019
+
+          - vs-version: vs2019
+            toolset-version: v142
+            os: windows-2022
+
+          - vs-version: vs2022
+            toolset-version: v143
+            os: windows-2022
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Configure CMake
+        shell: bash
+        run: |
+          mkdir build
+          cd build
+          cmake -T ${{ matrix.toolset-version}},host=x64 -A Win32 -D CMAKE_BUILD_TYPE=Release -DKALDI_NATIVE_FBANK_BUILD_PYTHON=OFF ..
+          cat CMakeCache.txt
+
+
+      - name: Build kaldi-native-fbank for windows
+        shell: bash
+        run: |
+          cd build
+          cmake --build . --target ALL_BUILD --config Release
+
+      - name: Run tests for windows
+        shell: bash
+        run: |
+          cd build
+
+          ctest --verbose -C Release --output-on-failure -E py
diff --git a/cli/fbank/sys/knf/.gitignore b/cli/fbank/sys/knf/.gitignore
new file mode 100644 (file)
index 0000000..ce74a7e
--- /dev/null
@@ -0,0 +1,8 @@
+build
+dist
+*.egg-info
+**/__pycache__
+build-arm-linux-gnueabihf
+
+# vim
+*.sw?
diff --git a/cli/fbank/sys/knf/CMakeLists.txt b/cli/fbank/sys/knf/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8d289be
--- /dev/null
@@ -0,0 +1,131 @@
+if("x${CMAKE_SOURCE_DIR}" STREQUAL "x${CMAKE_BINARY_DIR}")
+  message(FATAL_ERROR "\
+In-source build is not a good practice.
+Please use:
+  mkdir build
+  cd build
+  cmake ..
+to build this project"
+  )
+endif()
+
+if(CMAKE_TOOLCHAIN_FILE)
+  set(_BUILD_PYTHON OFF)
+  set(_BUILD_TESTS OFF)
+else()
+  set(_BUILD_PYTHON ON)
+  set(_BUILD_TESTS ON)
+endif()
+
+if(POLICY CMP0057)
+  cmake_policy(SET CMP0057 NEW)
+endif()
+
+cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
+
+project(kaldi-native-fbank CXX C)
+
+set(KALDI_NATIVE_FBANK_VERSION "1.20.0")
+
+# Disable warning about
+#
+# "The DOWNLOAD_EXTRACT_TIMESTAMP option was not given and policy CMP0135 is
+#  not set.
+if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
+  cmake_policy(SET CMP0135 NEW)
+endif()
+
+if(NOT CMAKE_BUILD_TYPE)
+  set(CMAKE_BUILD_TYPE Release)
+endif()
+
+message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
+
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
+
+set(CMAKE_SKIP_BUILD_RPATH FALSE)
+set(BUILD_RPATH_USE_ORIGIN TRUE)
+set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
+
+if(NOT APPLE)
+  set(kaldi_native_fbank_rpath_origin "$ORIGIN")
+else()
+  set(kaldi_native_fbank_rpath_origin "@loader_path")
+endif()
+
+set(CMAKE_INSTALL_RPATH ${kaldi_native_fbank_rpath_origin})
+set(CMAKE_BUILD_RPATH ${kaldi_native_fbank_rpath_origin})
+
+set(CMAKE_CXX_STANDARD 14 CACHE STRING "The C++ version to be used.")
+
+if(NOT DEFINED BUILD_SHARED_LIBS)
+  set(BUILD_SHARED_LIBS ON)
+endif()
+
+# See
+# https://stackoverflow.com/questions/33062728/cmake-link-shared-library-on-windows
+if(BUILD_SHARED_LIBS AND MSVC)
+  set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
+endif()
+message(STATUS "CMAKE_EXPORT_COMPILE_COMMANDS: ${CMAKE_EXPORT_COMPILE_COMMANDS}")
+
+message(STATUS "BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}")
+
+option(KALDI_NATIVE_FBANK_BUILD_TESTS "Whether to build tests or not" ${_BUILD_TESTS})
+option(KALDI_NATIVE_FBANK_BUILD_PYTHON "Whether to build Python extension" ${_BUILD_PYTHON})
+option(KALDI_NATIVE_FBANK_ENABLE_CHECK "Whether to build with log" OFF)
+
+message(STATUS "KALDI_NATIVE_FBANK_BUILD_TESTS: ${KALDI_NATIVE_FBANK_BUILD_TESTS}")
+message(STATUS "KALDI_NATIVE_FBANK_BUILD_PYTHON: ${KALDI_NATIVE_FBANK_BUILD_PYTHON}")
+message(STATUS "KALDI_NATIVE_FBANK_ENABLE_CHECK: ${KALDI_NATIVE_FBANK_ENABLE_CHECK}")
+
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules)
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
+
+message(STATUS "KALDI_NATIVE_FBANK_ENABLE_CHECK: ${KALDI_NATIVE_FBANK_ENABLE_CHECK}")
+
+if(WIN32)
+  add_definitions(-DNOMINMAX) # Otherwise, std::max() and std::min() won't work
+endif()
+
+if(KALDI_NATIVE_FBANK_BUILD_PYTHON)
+  include(pybind11)
+endif()
+
+if(KALDI_NATIVE_FBANK_BUILD_TESTS)
+  enable_testing()
+  include(googletest)
+endif()
+
+if(NOT CMAKE_INSTALL_PREFIX)
+  set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install")
+endif()
+
+message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
+message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
+
+include(CheckIncludeFileCXX)
+check_include_file_cxx(cxxabi.h KNF_HAVE_CXXABI_H)
+check_include_file_cxx(execinfo.h KNF_HAVE_EXECINFO_H)
+
+include_directories(${CMAKE_SOURCE_DIR})
+
+if(WIN32 AND MSVC)
+  # disable various warnings for MSVC
+  # 4244: '=': conversion from 'double' to 'float', possible loss of data
+  # 4267: 'return': conversion from 'size_t' to 'int32_t', possible loss of data
+  # 4624: destructor was implicitly defined as deleted because a base class destructor is inaccessible or deleted
+  set(disabled_warnings
+      /wd4244
+      /wd4267
+      /wd4624
+  )
+  message(STATUS "Disabled warnings: ${disabled_warnings}")
+  foreach(w IN LISTS disabled_warnings)
+    string(APPEND CMAKE_CXX_FLAGS " ${w} ")
+  endforeach()
+endif()
+
+add_subdirectory(kaldi-native-fbank)
diff --git a/cli/fbank/sys/knf/LICENSE b/cli/fbank/sys/knf/LICENSE
new file mode 100644 (file)
index 0000000..d645695
--- /dev/null
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/cli/fbank/sys/knf/MANIFEST.in b/cli/fbank/sys/knf/MANIFEST.in
new file mode 100644 (file)
index 0000000..ae2b482
--- /dev/null
@@ -0,0 +1,5 @@
+include LICENSE
+include README.md
+include CMakeLists.txt
+recursive-include kaldi-native-fbank *.*
+recursive-include cmake *.*
diff --git a/cli/fbank/sys/knf/README.md b/cli/fbank/sys/knf/README.md
new file mode 100644 (file)
index 0000000..c806ab6
--- /dev/null
@@ -0,0 +1,109 @@
+# Introduction
+
+Kaldi-compatible online fbank feature extractor without external dependencies.
+
+Tested on the following architectures and operating systems:
+
+  - Linux
+  - macOS
+  - Windows
+  - Android
+  - x86
+  - arm
+  - aarch64
+
+# Usage
+
+See the following CMake-based speech recognition (i.e., text-to-speech) projects
+for its usage:
+
+- <https://github.com/k2-fsa/sherpa-ncnn>
+  - Specifically, please have a look at <https://github.com/k2-fsa/sherpa-ncnn/blob/master/sherpa-ncnn/csrc/features.h>
+- <https://github.com/k2-fsa/sherpa-onnx>
+
+They use `kaldi-native-fbank` to compute fbank features for **real-time**
+speech recognition.
+
+# Python APIs
+
+First, please install `kaldi-native-fbank` by
+
+```bash
+git clone https://github.com/csukuangfj/kaldi-native-fbank
+cd kaldi-native-fbank
+python3 setup.py install
+```
+
+or use
+
+```bash
+pip install kaldi-native-fbank
+```
+
+To check that you have installed `kaldi-native-fbank` successfully, please use
+
+```
+python3 -c "import kaldi_native_fbank; print(kaldi_native_fbank.__version__)"
+```
+
+which should print the version you have installed.
+
+Please refer to
+
+  - <https://github.com/csukuangfj/kaldi-native-fbank/blob/master/kaldi-native-fbank/python/tests/test_online_fbank.py>
+  - <https://github.com/csukuangfj/kaldi-native-fbank/blob/master/kaldi-native-fbank/python/tests/test_online_mfcc.py>
+
+for usages.
+
+For easier reference, we post the above file below:
+
+```python3
+#!/usr/bin/env python3
+
+import sys
+
+try:
+    import kaldifeat
+except:
+    print("Please install kaldifeat first")
+    sys.exit(0)
+
+import kaldi_native_fbank as knf
+import torch
+
+
+def main():
+    sampling_rate = 16000
+    samples = torch.randn(16000 * 10)
+
+    opts = kaldifeat.FbankOptions()
+    opts.frame_opts.dither = 0
+    opts.mel_opts.num_bins = 80
+    opts.frame_opts.snip_edges = False
+    opts.mel_opts.debug_mel = False
+
+    online_fbank = kaldifeat.OnlineFbank(opts)
+
+    online_fbank.accept_waveform(sampling_rate, samples)
+
+    opts = knf.FbankOptions()
+    opts.frame_opts.dither = 0
+    opts.mel_opts.num_bins = 80
+    opts.frame_opts.snip_edges = False
+    opts.mel_opts.debug_mel = False
+
+    fbank = knf.OnlineFbank(opts)
+    fbank.accept_waveform(sampling_rate, samples.tolist())
+
+    assert online_fbank.num_frames_ready == fbank.num_frames_ready
+    for i in range(fbank.num_frames_ready):
+        f1 = online_fbank.get_frame(i)
+        f2 = torch.from_numpy(fbank.get_frame(i))
+        assert torch.allclose(f1, f2, atol=1e-3), (i, (f1 - f2).abs().max())
+
+
+if __name__ == "__main__":
+    torch.manual_seed(20220825)
+    main()
+    print("success")
+```
diff --git a/cli/fbank/sys/knf/build-arm-linux-gnueabihf.sh b/cli/fbank/sys/knf/build-arm-linux-gnueabihf.sh
new file mode 100755 (executable)
index 0000000..e8b9e25
--- /dev/null
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -x
+
+mkdir -p build-arm-linux-gnueabihf
+cd build-arm-linux-gnueabihf
+cmake -DCMAKE_TOOLCHAIN_FILE=../toolchains/arm-linux-gnueabihf.toolchain.cmake ..
+make VERBOSE=1 -j4
+make install
diff --git a/cli/fbank/sys/knf/cmake/Modules/FetchContent.cmake b/cli/fbank/sys/knf/cmake/Modules/FetchContent.cmake
new file mode 100644 (file)
index 0000000..98cdf6c
--- /dev/null
@@ -0,0 +1,916 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file Copyright.txt or https://cmake.org/licensing for details.
+
+#[=======================================================================[.rst:
+FetchContent
+------------------
+
+.. only:: html
+
+  .. contents::
+
+Overview
+^^^^^^^^
+
+This module enables populating content at configure time via any method
+supported by the :module:`ExternalProject` module.  Whereas
+:command:`ExternalProject_Add` downloads at build time, the
+``FetchContent`` module makes content available immediately, allowing the
+configure step to use the content in commands like :command:`add_subdirectory`,
+:command:`include` or :command:`file` operations.
+
+Content population details would normally be defined separately from the
+command that performs the actual population.  Projects should also
+check whether the content has already been populated somewhere else in the
+project hierarchy.  Typical usage would look something like this:
+
+.. code-block:: cmake
+
+  FetchContent_Declare(
+    googletest
+    GIT_REPOSITORY https://github.com/google/googletest.git
+    GIT_TAG        release-1.8.0
+  )
+
+  FetchContent_GetProperties(googletest)
+  if(NOT googletest_POPULATED)
+    FetchContent_Populate(googletest)
+    add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
+  endif()
+
+When using the above pattern with a hierarchical project arrangement,
+projects at higher levels in the hierarchy are able to define or override
+the population details of content specified anywhere lower in the project
+hierarchy.  The ability to detect whether content has already been
+populated ensures that even if multiple child projects want certain content
+to be available, the first one to populate it wins.  The other child project
+can simply make use of the already available content instead of repeating
+the population for itself.  See the
+:ref:`Examples <fetch-content-examples>` section which demonstrates
+this scenario.
+
+The ``FetchContent`` module also supports defining and populating
+content in a single call, with no check for whether the content has been
+populated elsewhere in the project already.  This is a more low level
+operation and would not normally be the way the module is used, but it is
+sometimes useful as part of implementing some higher level feature or to
+populate some content in CMake's script mode.
+
+
+Declaring Content Details
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. command:: FetchContent_Declare
+
+  .. code-block:: cmake
+
+    FetchContent_Declare(<name> <contentOptions>...)
+
+  The ``FetchContent_Declare()`` function records the options that describe
+  how to populate the specified content, but if such details have already
+  been recorded earlier in this project (regardless of where in the project
+  hierarchy), this and all later calls for the same content ``<name>`` are
+  ignored.  This "first to record, wins" approach is what allows hierarchical
+  projects to have parent projects override content details of child projects.
+
+  The content ``<name>`` can be any string without spaces, but good practice
+  would be to use only letters, numbers and underscores.  The name will be
+  treated case-insensitively and it should be obvious for the content it
+  represents, often being the name of the child project or the value given
+  to its top level :command:`project` command (if it is a CMake project).
+  For well-known public projects, the name should generally be the official
+  name of the project.  Choosing an unusual name makes it unlikely that other
+  projects needing that same content will use the same name, leading to
+  the content being populated multiple times.
+
+  The ``<contentOptions>`` can be any of the download or update/patch options
+  that the :command:`ExternalProject_Add` command understands.  The configure,
+  build, install and test steps are explicitly disabled and therefore options
+  related to them will be ignored.  In most cases, ``<contentOptions>`` will
+  just be a couple of options defining the download method and method-specific
+  details like a commit tag or archive hash.  For example:
+
+  .. code-block:: cmake
+
+    FetchContent_Declare(
+      googletest
+      GIT_REPOSITORY https://github.com/google/googletest.git
+      GIT_TAG        release-1.8.0
+    )
+
+    FetchContent_Declare(
+      myCompanyIcons
+      URL      https://intranet.mycompany.com/assets/iconset_1.12.tar.gz
+      URL_HASH 5588a7b18261c20068beabfb4f530b87
+    )
+
+    FetchContent_Declare(
+      myCompanyCertificates
+      SVN_REPOSITORY svn+ssh://svn.mycompany.com/srv/svn/trunk/certs
+      SVN_REVISION   -r12345
+    )
+
+Populating The Content
+^^^^^^^^^^^^^^^^^^^^^^
+
+.. command:: FetchContent_Populate
+
+  .. code-block:: cmake
+
+    FetchContent_Populate( <name> )
+
+  In most cases, the only argument given to ``FetchContent_Populate()`` is the
+  ``<name>``.  When used this way, the command assumes the content details have
+  been recorded by an earlier call to :command:`FetchContent_Declare`.  The
+  details are stored in a global property, so they are unaffected by things
+  like variable or directory scope.  Therefore, it doesn't matter where in the
+  project the details were previously declared, as long as they have been
+  declared before the call to ``FetchContent_Populate()``.  Those saved details
+  are then used to construct a call to :command:`ExternalProject_Add` in a
+  private sub-build to perform the content population immediately.  The
+  implementation of ``ExternalProject_Add()`` ensures that if the content has
+  already been populated in a previous CMake run, that content will be reused
+  rather than repopulating them again.  For the common case where population
+  involves downloading content, the cost of the download is only paid once.
+
+  An internal global property records when a particular content population
+  request has been processed.  If ``FetchContent_Populate()`` is called more
+  than once for the same content name within a configure run, the second call
+  will halt with an error.  Projects can and should check whether content
+  population has already been processed with the
+  :command:`FetchContent_GetProperties` command before calling
+  ``FetchContent_Populate()``.
+
+  ``FetchContent_Populate()`` will set three variables in the scope of the
+  caller; ``<lcName>_POPULATED``, ``<lcName>_SOURCE_DIR`` and
+  ``<lcName>_BINARY_DIR``, where ``<lcName>`` is the lowercased ``<name>``.
+  ``<lcName>_POPULATED`` will always be set to ``True`` by the call.
+  ``<lcName>_SOURCE_DIR`` is the location where the
+  content can be found upon return (it will have already been populated), while
+  ``<lcName>_BINARY_DIR`` is a directory intended for use as a corresponding
+  build directory.  The main use case for the two directory variables is to
+  call :command:`add_subdirectory` immediately after population, i.e.:
+
+  .. code-block:: cmake
+
+    FetchContent_Populate(FooBar ...)
+    add_subdirectory(${foobar_SOURCE_DIR} ${foobar_BINARY_DIR})
+
+  The values of the three variables can also be retrieved from anywhere in the
+  project hierarchy using the :command:`FetchContent_GetProperties` command.
+
+  A number of cache variables influence the behavior of all content population
+  performed using details saved from a :command:`FetchContent_Declare` call:
+
+  ``FETCHCONTENT_BASE_DIR``
+    In most cases, the saved details do not specify any options relating to the
+    directories to use for the internal sub-build, final source and build areas.
+    It is generally best to leave these decisions up to the ``FetchContent``
+    module to handle on the project's behalf.  The ``FETCHCONTENT_BASE_DIR``
+    cache variable controls the point under which all content population
+    directories are collected, but in most cases developers would not need to
+    change this.  The default location is ``${CMAKE_BINARY_DIR}/_deps``, but if
+    developers change this value, they should aim to keep the path short and
+    just below the top level of the build tree to avoid running into path
+    length problems on Windows.
+
+  ``FETCHCONTENT_QUIET``
+    The logging output during population can be quite verbose, making the
+    configure stage quite noisy.  This cache option (``ON`` by default) hides
+    all population output unless an error is encountered.  If experiencing
+    problems with hung downloads, temporarily switching this option off may
+    help diagnose which content population is causing the issue.
+
+  ``FETCHCONTENT_FULLY_DISCONNECTED``
+    When this option is enabled, no attempt is made to download or update
+    any content.  It is assumed that all content has already been populated in
+    a previous run or the source directories have been pointed at existing
+    contents the developer has provided manually (using options described
+    further below).  When the developer knows that no changes have been made to
+    any content details, turning this option ``ON`` can significantly speed up
+    the configure stage.  It is ``OFF`` by default.
+
+  ``FETCHCONTENT_UPDATES_DISCONNECTED``
+    This is a less severe download/update control compared to
+    ``FETCHCONTENT_FULLY_DISCONNECTED``.  Instead of bypassing all download and
+    update logic, the ``FETCHCONTENT_UPDATES_DISCONNECTED`` only disables the
+    update stage.  Therefore, if content has not been downloaded previously,
+    it will still be downloaded when this option is enabled.  This can speed up
+    the configure stage, but not as much as
+    ``FETCHCONTENT_FULLY_DISCONNECTED``.  It is ``OFF`` by default.
+
+  In addition to the above cache variables, the following cache variables are
+  also defined for each content name (``<ucName>`` is the uppercased value of
+  ``<name>``):
+
+  ``FETCHCONTENT_SOURCE_DIR_<ucName>``
+    If this is set, no download or update steps are performed for the specified
+    content and the ``<lcName>_SOURCE_DIR`` variable returned to the caller is
+    pointed at this location.  This gives developers a way to have a separate
+    checkout of the content that they can modify freely without interference
+    from the build.  The build simply uses that existing source, but it still
+    defines ``<lcName>_BINARY_DIR`` to point inside its own build area.
+    Developers are strongly encouraged to use this mechanism rather than
+    editing the sources populated in the default location, as changes to
+    sources in the default location can be lost when content population details
+    are changed by the project.
+
+  ``FETCHCONTENT_UPDATES_DISCONNECTED_<ucName>``
+    This is the per-content equivalent of
+    ``FETCHCONTENT_UPDATES_DISCONNECTED``. If the global option or this option
+    is ``ON``, then updates will be disabled for the named content.
+    Disabling updates for individual content can be useful for content whose
+    details rarely change, while still leaving other frequently changing
+    content with updates enabled.
+
+
+  The ``FetchContent_Populate()`` command also supports a syntax allowing the
+  content details to be specified directly rather than using any saved
+  details.  This is more low-level and use of this form is generally to be
+  avoided in favour of using saved content details as outlined above.
+  Nevertheless, in certain situations it can be useful to invoke the content
+  population as an isolated operation (typically as part of implementing some
+  other higher level feature or when using CMake in script mode):
+
+  .. code-block:: cmake
+
+    FetchContent_Populate( <name>
+      [QUIET]
+      [SUBBUILD_DIR <subBuildDir>]
+      [SOURCE_DIR <srcDir>]
+      [BINARY_DIR <binDir>]
+      ...
+    )
+
+  This form has a number of key differences to that where only ``<name>`` is
+  provided:
+
+  - All required population details are assumed to have been provided directly
+    in the call to ``FetchContent_Populate()``. Any saved details for
+    ``<name>`` are ignored.
+  - No check is made for whether content for ``<name>`` has already been
+    populated.
+  - No global property is set to record that the population has occurred.
+  - No global properties record the source or binary directories used for the
+    populated content.
+  - The ``FETCHCONTENT_FULLY_DISCONNECTED`` and
+    ``FETCHCONTENT_UPDATES_DISCONNECTED`` cache variables are ignored.
+
+  The ``<lcName>_SOURCE_DIR`` and ``<lcName>_BINARY_DIR`` variables are still
+  returned to the caller, but since these locations are not stored as global
+  properties when this form is used, they are only available to the calling
+  scope and below rather than the entire project hierarchy.  No
+  ``<lcName>_POPULATED`` variable is set in the caller's scope with this form.
+
+  The supported options for ``FetchContent_Populate()`` are the same as those
+  for :command:`FetchContent_Declare()`.  Those few options shown just
+  above are either specific to ``FetchContent_Populate()`` or their behavior is
+  slightly modified from how :command:`ExternalProject_Add` treats them.
+
+  ``QUIET``
+    The ``QUIET`` option can be given to hide the output associated with
+    populating the specified content.  If the population fails, the output will
+    be shown regardless of whether this option was given or not so that the
+    cause of the failure can be diagnosed.  The global ``FETCHCONTENT_QUIET``
+    cache variable has no effect on ``FetchContent_Populate()`` calls where the
+    content details are provided directly.
+
+  ``SUBBUILD_DIR``
+    The ``SUBBUILD_DIR`` argument can be provided to change the location of the
+    sub-build created to perform the population.  The default value is
+    ``${CMAKE_CURRENT_BINARY_DIR}/<lcName>-subbuild`` and it would be unusual
+    to need to override this default.  If a relative path is specified, it will
+    be interpreted as relative to :variable:`CMAKE_CURRENT_BINARY_DIR`.
+
+  ``SOURCE_DIR``, ``BINARY_DIR``
+    The ``SOURCE_DIR`` and ``BINARY_DIR`` arguments are supported by
+    :command:`ExternalProject_Add`, but different default values are used by
+    ``FetchContent_Populate()``.  ``SOURCE_DIR`` defaults to
+    ``${CMAKE_CURRENT_BINARY_DIR}/<lcName>-src`` and ``BINARY_DIR`` defaults to
+    ``${CMAKE_CURRENT_BINARY_DIR}/<lcName>-build``.  If a relative path is
+    specified, it will be interpreted as relative to
+    :variable:`CMAKE_CURRENT_BINARY_DIR`.
+
+  In addition to the above explicit options, any other unrecognized options are
+  passed through unmodified to :command:`ExternalProject_Add` to perform the
+  download, patch and update steps.  The following options are explicitly
+  prohibited (they are disabled by the ``FetchContent_Populate()`` command):
+
+  - ``CONFIGURE_COMMAND``
+  - ``BUILD_COMMAND``
+  - ``INSTALL_COMMAND``
+  - ``TEST_COMMAND``
+
+  If using ``FetchContent_Populate()`` within CMake's script mode, be aware
+  that the implementation sets up a sub-build which therefore requires a CMake
+  generator and build tool to be available. If these cannot be found by
+  default, then the :variable:`CMAKE_GENERATOR` and/or
+  :variable:`CMAKE_MAKE_PROGRAM` variables will need to be set appropriately
+  on the command line invoking the script.
+
+
+Retrieve Population Properties
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. command:: FetchContent_GetProperties
+
+  When using saved content details, a call to :command:`FetchContent_Populate`
+  records information in global properties which can be queried at any time.
+  This information includes the source and binary directories associated with
+  the content and also whether or not the content population has been processed
+  during the current configure run.
+
+  .. code-block:: cmake
+
+    FetchContent_GetProperties( <name>
+      [SOURCE_DIR <srcDirVar>]
+      [BINARY_DIR <binDirVar>]
+      [POPULATED <doneVar>]
+    )
+
+  The ``SOURCE_DIR``, ``BINARY_DIR`` and ``POPULATED`` options can be used to
+  specify which properties should be retrieved.  Each option accepts a value
+  which is the name of the variable in which to store that property.  Most of
+  the time though, only ``<name>`` is given, in which case the call will then
+  set the same variables as a call to
+  :command:`FetchContent_Populate(name) <FetchContent_Populate>`.  This allows
+  the following canonical pattern to be used, which ensures that the relevant
+  variables will always be defined regardless of whether or not the population
+  has been performed elsewhere in the project already:
+
+  .. code-block:: cmake
+
+    FetchContent_GetProperties(foobar)
+    if(NOT foobar_POPULATED)
+      FetchContent_Populate(foobar)
+
+      # Set any custom variables, etc. here, then
+      # populate the content as part of this build
+
+      add_subdirectory(${foobar_SOURCE_DIR} ${foobar_BINARY_DIR})
+    endif()
+
+  The above pattern allows other parts of the overall project hierarchy to
+  re-use the same content and ensure that it is only populated once.
+
+
+.. _`fetch-content-examples`:
+
+Examples
+^^^^^^^^
+
+Consider a project hierarchy where ``projA`` is the top level project and it
+depends on projects ``projB`` and ``projC``. Both ``projB`` and ``projC``
+can be built standalone and they also both depend on another project
+``projD``.  For simplicity, this example will assume that all four projects
+are available on a company git server.  The ``CMakeLists.txt`` of each project
+might have sections like the following:
+
+*projA*:
+
+.. code-block:: cmake
+
+  include(FetchContent)
+  FetchContent_Declare(
+    projB
+    GIT_REPOSITORY git@mycompany.com/git/projB.git
+    GIT_TAG        4a89dc7e24ff212a7b5167bef7ab079d
+  )
+  FetchContent_Declare(
+    projC
+    GIT_REPOSITORY git@mycompany.com/git/projC.git
+    GIT_TAG        4ad4016bd1d8d5412d135cf8ceea1bb9
+  )
+  FetchContent_Declare(
+    projD
+    GIT_REPOSITORY git@mycompany.com/git/projD.git
+    GIT_TAG        origin/integrationBranch
+  )
+
+  FetchContent_GetProperties(projB)
+  if(NOT projb_POPULATED)
+    FetchContent_Populate(projB)
+    add_subdirectory(${projb_SOURCE_DIR} ${projb_BINARY_DIR})
+  endif()
+
+  FetchContent_GetProperties(projC)
+  if(NOT projc_POPULATED)
+    FetchContent_Populate(projC)
+    add_subdirectory(${projc_SOURCE_DIR} ${projc_BINARY_DIR})
+  endif()
+
+*projB*:
+
+.. code-block:: cmake
+
+  include(FetchContent)
+  FetchContent_Declare(
+    projD
+    GIT_REPOSITORY git@mycompany.com/git/projD.git
+    GIT_TAG        20b415f9034bbd2a2e8216e9a5c9e632
+  )
+
+  FetchContent_GetProperties(projD)
+  if(NOT projd_POPULATED)
+    FetchContent_Populate(projD)
+    add_subdirectory(${projd_SOURCE_DIR} ${projd_BINARY_DIR})
+  endif()
+
+
+*projC*:
+
+.. code-block:: cmake
+
+  include(FetchContent)
+  FetchContent_Declare(
+    projD
+    GIT_REPOSITORY git@mycompany.com/git/projD.git
+    GIT_TAG        7d9a17ad2c962aa13e2fbb8043fb6b8a
+  )
+
+  FetchContent_GetProperties(projD)
+  if(NOT projd_POPULATED)
+    FetchContent_Populate(projD)
+    add_subdirectory(${projd_SOURCE_DIR} ${projd_BINARY_DIR})
+  endif()
+
+A few key points should be noted in the above:
+
+- ``projB`` and ``projC`` define different content details for ``projD``,
+  but ``projA`` also defines a set of content details for ``projD`` and
+  because ``projA`` will define them first, the details from ``projB`` and
+  ``projC`` will not be used.  The override details defined by ``projA``
+  are not required to match either of those from ``projB`` or ``projC``, but
+  it is up to the higher level project to ensure that the details it does
+  define still make sense for the child projects.
+- While ``projA`` defined content details for ``projD``, it did not need
+  to explicitly call ``FetchContent_Populate(projD)`` itself.  Instead, it
+  leaves that to a child project to do (in this case it will be ``projB``
+  since it is added to the build ahead of ``projC``).  If ``projA`` needed to
+  customize how the ``projD`` content was brought into the build as well
+  (e.g. define some CMake variables before calling
+  :command:`add_subdirectory` after populating), it would do the call to
+  ``FetchContent_Populate()``, etc. just as it did for the ``projB`` and
+  ``projC`` content.  For higher level projects, it is usually enough to
+  just define the override content details and leave the actual population
+  to the child projects.  This saves repeating the same thing at each level
+  of the project hierarchy unnecessarily.
+- Even though ``projA`` is the top level project in this example, it still
+  checks whether ``projB`` and ``projC`` have already been populated before
+  going ahead to do those populations.  This makes ``projA`` able to be more
+  easily incorporated as a child of some other higher level project in the
+  future if required.  Always protect a call to
+  :command:`FetchContent_Populate` with a check to
+  :command:`FetchContent_GetProperties`, even in what may be considered a top
+  level project at the time.
+
+
+The following example demonstrates how one might download and unpack a
+firmware tarball using CMake's :manual:`script mode <cmake(1)>`.  The call to
+:command:`FetchContent_Populate` specifies all the content details and the
+unpacked firmware will be placed in a ``firmware`` directory below the
+current working directory.
+
+*getFirmware.cmake*:
+
+.. code-block:: cmake
+
+  # NOTE: Intended to be run in script mode with cmake -P
+  include(FetchContent)
+  FetchContent_Populate(
+    firmware
+    URL        https://mycompany.com/assets/firmware-1.23-arm.tar.gz
+    URL_HASH   MD5=68247684da89b608d466253762b0ff11
+    SOURCE_DIR firmware
+  )
+
+#]=======================================================================]
+
+
+set(__FetchContent_privateDir "${CMAKE_CURRENT_LIST_DIR}/FetchContent")
+
+#=======================================================================
+# Recording and retrieving content details for later population
+#=======================================================================
+
+# Internal use, projects must not call this directly. It is
+# intended for use by FetchContent_Declare() only.
+#
+# Sets a content-specific global property (not meant for use
+# outside of functions defined here in this file) which can later
+# be retrieved using __FetchContent_getSavedDetails() with just the
+# same content name. If there is already a value stored in the
+# property, it is left unchanged and this call has no effect.
+# This allows parent projects to define the content details,
+# overriding anything a child project may try to set (properties
+# are not cached between runs, so the first thing to set it in a
+# build will be in control).
+function(__FetchContent_declareDetails contentName)
+
+  string(TOLOWER ${contentName} contentNameLower)
+  set(propertyName "_FetchContent_${contentNameLower}_savedDetails")
+  get_property(alreadyDefined GLOBAL PROPERTY ${propertyName} DEFINED)
+  if(NOT alreadyDefined)
+    define_property(GLOBAL PROPERTY ${propertyName}
+      BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
+      FULL_DOCS  "Details used by FetchContent_Populate() for ${contentName}"
+    )
+    set_property(GLOBAL PROPERTY ${propertyName} ${ARGN})
+  endif()
+
+endfunction()
+
+
+# Internal use, projects must not call this directly. It is
+# intended for use by the FetchContent_Declare() function.
+#
+# Retrieves details saved for the specified content in an
+# earlier call to __FetchContent_declareDetails().
+function(__FetchContent_getSavedDetails contentName outVar)
+
+  string(TOLOWER ${contentName} contentNameLower)
+  set(propertyName "_FetchContent_${contentNameLower}_savedDetails")
+  get_property(alreadyDefined GLOBAL PROPERTY ${propertyName} DEFINED)
+  if(NOT alreadyDefined)
+    message(FATAL_ERROR "No content details recorded for ${contentName}")
+  endif()
+  get_property(propertyValue GLOBAL PROPERTY ${propertyName})
+  set(${outVar} "${propertyValue}" PARENT_SCOPE)
+
+endfunction()
+
+
+# Saves population details of the content, sets defaults for the
+# SOURCE_DIR and BUILD_DIR.
+function(FetchContent_Declare contentName)
+
+  set(options "")
+  set(oneValueArgs SVN_REPOSITORY)
+  set(multiValueArgs "")
+
+  cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+  unset(srcDirSuffix)
+  unset(svnRepoArgs)
+  if(ARG_SVN_REPOSITORY)
+    # Add a hash of the svn repository URL to the source dir. This works
+    # around the problem where if the URL changes, the download would
+    # fail because it tries to checkout/update rather than switch the
+    # old URL to the new one. We limit the hash to the first 7 characters
+    # so that the source path doesn't get overly long (which can be a
+    # problem on windows due to path length limits).
+    string(SHA1 urlSHA ${ARG_SVN_REPOSITORY})
+    string(SUBSTRING ${urlSHA} 0 7 urlSHA)
+    set(srcDirSuffix "-${urlSHA}")
+    set(svnRepoArgs  SVN_REPOSITORY ${ARG_SVN_REPOSITORY})
+  endif()
+
+  string(TOLOWER ${contentName} contentNameLower)
+  __FetchContent_declareDetails(
+    ${contentNameLower}
+    SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src${srcDirSuffix}"
+    BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build"
+    ${svnRepoArgs}
+    # List these last so they can override things we set above
+    ${ARG_UNPARSED_ARGUMENTS}
+  )
+
+endfunction()
+
+
+#=======================================================================
+# Set/get whether the specified content has been populated yet.
+# The setter also records the source and binary dirs used.
+#=======================================================================
+
+# Internal use, projects must not call this directly. It is
+# intended for use by the FetchContent_Populate() function to
+# record when FetchContent_Populate() is called for a particular
+# content name.
+function(__FetchContent_setPopulated contentName sourceDir binaryDir)
+
+  string(TOLOWER ${contentName} contentNameLower)
+  set(prefix "_FetchContent_${contentNameLower}")
+
+  set(propertyName "${prefix}_sourceDir")
+  define_property(GLOBAL PROPERTY ${propertyName}
+    BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
+    FULL_DOCS  "Details used by FetchContent_Populate() for ${contentName}"
+  )
+  set_property(GLOBAL PROPERTY ${propertyName} ${sourceDir})
+
+  set(propertyName "${prefix}_binaryDir")
+  define_property(GLOBAL PROPERTY ${propertyName}
+    BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
+    FULL_DOCS  "Details used by FetchContent_Populate() for ${contentName}"
+  )
+  set_property(GLOBAL PROPERTY ${propertyName} ${binaryDir})
+
+  set(propertyName "${prefix}_populated")
+  define_property(GLOBAL PROPERTY ${propertyName}
+    BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
+    FULL_DOCS  "Details used by FetchContent_Populate() for ${contentName}"
+  )
+  set_property(GLOBAL PROPERTY ${propertyName} True)
+
+endfunction()
+
+
+# Set variables in the calling scope for any of the retrievable
+# properties. If no specific properties are requested, variables
+# will be set for all retrievable properties.
+#
+# This function is intended to also be used by projects as the canonical
+# way to detect whether they should call FetchContent_Populate()
+# and pull the populated source into the build with add_subdirectory(),
+# if they are using the populated content in that way.
+function(FetchContent_GetProperties contentName)
+
+  string(TOLOWER ${contentName} contentNameLower)
+
+  set(options "")
+  set(oneValueArgs SOURCE_DIR BINARY_DIR POPULATED)
+  set(multiValueArgs "")
+
+  cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+  if(NOT ARG_SOURCE_DIR AND
+     NOT ARG_BINARY_DIR AND
+     NOT ARG_POPULATED)
+    # No specific properties requested, provide them all
+    set(ARG_SOURCE_DIR ${contentNameLower}_SOURCE_DIR)
+    set(ARG_BINARY_DIR ${contentNameLower}_BINARY_DIR)
+    set(ARG_POPULATED  ${contentNameLower}_POPULATED)
+  endif()
+
+  set(prefix "_FetchContent_${contentNameLower}")
+
+  if(ARG_SOURCE_DIR)
+    set(propertyName "${prefix}_sourceDir")
+    get_property(value GLOBAL PROPERTY ${propertyName})
+    if(value)
+      set(${ARG_SOURCE_DIR} ${value} PARENT_SCOPE)
+    endif()
+  endif()
+
+  if(ARG_BINARY_DIR)
+    set(propertyName "${prefix}_binaryDir")
+    get_property(value GLOBAL PROPERTY ${propertyName})
+    if(value)
+      set(${ARG_BINARY_DIR} ${value} PARENT_SCOPE)
+    endif()
+  endif()
+
+  if(ARG_POPULATED)
+    set(propertyName "${prefix}_populated")
+    get_property(value GLOBAL PROPERTY ${propertyName} DEFINED)
+    set(${ARG_POPULATED} ${value} PARENT_SCOPE)
+  endif()
+
+endfunction()
+
+
+#=======================================================================
+# Performing the population
+#=======================================================================
+
+# The value of contentName will always have been lowercased by the caller.
+# All other arguments are assumed to be options that are understood by
+# ExternalProject_Add(), except for QUIET and SUBBUILD_DIR.
+function(__FetchContent_directPopulate contentName)
+
+  set(options
+      QUIET
+  )
+  set(oneValueArgs
+      SUBBUILD_DIR
+      SOURCE_DIR
+      BINARY_DIR
+      # Prevent the following from being passed through
+      CONFIGURE_COMMAND
+      BUILD_COMMAND
+      INSTALL_COMMAND
+      TEST_COMMAND
+  )
+  set(multiValueArgs "")
+
+  cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+  if(NOT ARG_SUBBUILD_DIR)
+    message(FATAL_ERROR "Internal error: SUBBUILD_DIR not set")
+  elseif(NOT IS_ABSOLUTE "${ARG_SUBBUILD_DIR}")
+    set(ARG_SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_SUBBUILD_DIR}")
+  endif()
+
+  if(NOT ARG_SOURCE_DIR)
+    message(FATAL_ERROR "Internal error: SOURCE_DIR not set")
+  elseif(NOT IS_ABSOLUTE "${ARG_SOURCE_DIR}")
+    set(ARG_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_SOURCE_DIR}")
+  endif()
+
+  if(NOT ARG_BINARY_DIR)
+    message(FATAL_ERROR "Internal error: BINARY_DIR not set")
+  elseif(NOT IS_ABSOLUTE "${ARG_BINARY_DIR}")
+    set(ARG_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_BINARY_DIR}")
+  endif()
+
+  # Ensure the caller can know where to find the source and build directories
+  # with some convenient variables. Doing this here ensures the caller sees
+  # the correct result in the case where the default values are overridden by
+  # the content details set by the project.
+  set(${contentName}_SOURCE_DIR "${ARG_SOURCE_DIR}" PARENT_SCOPE)
+  set(${contentName}_BINARY_DIR "${ARG_BINARY_DIR}" PARENT_SCOPE)
+
+  # The unparsed arguments may contain spaces, so build up ARG_EXTRA
+  # in such a way that it correctly substitutes into the generated
+  # CMakeLists.txt file with each argument quoted.
+  unset(ARG_EXTRA)
+  foreach(arg IN LISTS ARG_UNPARSED_ARGUMENTS)
+    set(ARG_EXTRA "${ARG_EXTRA} \"${arg}\"")
+  endforeach()
+
+  # Hide output if requested, but save it to a variable in case there's an
+  # error so we can show the output upon failure. When not quiet, don't
+  # capture the output to a variable because the user may want to see the
+  # output as it happens (e.g. progress during long downloads). Combine both
+  # stdout and stderr in the one capture variable so the output stays in order.
+  if (ARG_QUIET)
+    set(outputOptions
+        OUTPUT_VARIABLE capturedOutput
+        ERROR_VARIABLE  capturedOutput
+    )
+  else()
+    set(capturedOutput)
+    set(outputOptions)
+    message(STATUS "Populating ${contentName}")
+  endif()
+
+  if(CMAKE_GENERATOR)
+    set(generatorOpts "-G${CMAKE_GENERATOR}")
+    if(CMAKE_GENERATOR_PLATFORM)
+      list(APPEND generatorOpts "-A${CMAKE_GENERATOR_PLATFORM}")
+    endif()
+    if(CMAKE_GENERATOR_TOOLSET)
+      list(APPEND generatorOpts "-T${CMAKE_GENERATOR_TOOLSET}")
+    endif()
+
+    if(CMAKE_MAKE_PROGRAM)
+      list(APPEND generatorOpts "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}")
+    endif()
+
+  else()
+    # Likely we've been invoked via CMake's script mode where no
+    # generator is set (and hence CMAKE_MAKE_PROGRAM could not be
+    # trusted even if provided). We will have to rely on being
+    # able to find the default generator and build tool.
+    unset(generatorOpts)
+  endif()
+
+  # Create and build a separate CMake project to carry out the population.
+  # If we've already previously done these steps, they will not cause
+  # anything to be updated, so extra rebuilds of the project won't occur.
+  # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project
+  # has this set to something not findable on the PATH.
+  configure_file("${__FetchContent_privateDir}/CMakeLists.cmake.in"
+                 "${ARG_SUBBUILD_DIR}/CMakeLists.txt")
+  execute_process(
+    COMMAND ${CMAKE_COMMAND} ${generatorOpts} .
+    RESULT_VARIABLE result
+    ${outputOptions}
+    WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}"
+  )
+  if(result)
+    if(capturedOutput)
+      message("${capturedOutput}")
+    endif()
+    message(FATAL_ERROR "CMake step for ${contentName} failed: ${result}")
+  endif()
+  execute_process(
+    COMMAND ${CMAKE_COMMAND} --build .
+    RESULT_VARIABLE result
+    ${outputOptions}
+    WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}"
+  )
+  if(result)
+    if(capturedOutput)
+      message("${capturedOutput}")
+    endif()
+    message(FATAL_ERROR "Build step for ${contentName} failed: ${result}")
+  endif()
+
+endfunction()
+
+
+option(FETCHCONTENT_FULLY_DISCONNECTED   "Disables all attempts to download or update content and assumes source dirs already exist")
+option(FETCHCONTENT_UPDATES_DISCONNECTED "Enables UPDATE_DISCONNECTED behavior for all content population")
+option(FETCHCONTENT_QUIET                "Enables QUIET option for all content population" ON)
+set(FETCHCONTENT_BASE_DIR "${CMAKE_BINARY_DIR}/_deps" CACHE PATH "Directory under which to collect all populated content")
+
+# Populate the specified content using details stored from
+# an earlier call to FetchContent_Declare().
+function(FetchContent_Populate contentName)
+
+  if(NOT contentName)
+    message(FATAL_ERROR "Empty contentName not allowed for FetchContent_Populate()")
+  endif()
+
+  string(TOLOWER ${contentName} contentNameLower)
+
+  if(ARGN)
+    # This is the direct population form with details fully specified
+    # as part of the call, so we already have everything we need
+    __FetchContent_directPopulate(
+      ${contentNameLower}
+      SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-subbuild"
+      SOURCE_DIR   "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-src"
+      BINARY_DIR   "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-build"
+      ${ARGN}  # Could override any of the above ..._DIR variables
+    )
+
+    # Pass source and binary dir variables back to the caller
+    set(${contentNameLower}_SOURCE_DIR "${${contentNameLower}_SOURCE_DIR}" PARENT_SCOPE)
+    set(${contentNameLower}_BINARY_DIR "${${contentNameLower}_BINARY_DIR}" PARENT_SCOPE)
+
+    # Don't set global properties, or record that we did this population, since
+    # this was a direct call outside of the normal declared details form.
+    # We only want to save values in the global properties for content that
+    # honours the hierarchical details mechanism so that projects are not
+    # robbed of the ability to override details set in nested projects.
+    return()
+  endif()
+
+  # No details provided, so assume they were saved from an earlier call
+  # to FetchContent_Declare(). Do a check that we haven't already
+  # populated this content before in case the caller forgot to check.
+  FetchContent_GetProperties(${contentName})
+  if(${contentNameLower}_POPULATED)
+    message(FATAL_ERROR "Content ${contentName} already populated in ${${contentNameLower}_SOURCE_DIR}")
+  endif()
+
+  string(TOUPPER ${contentName} contentNameUpper)
+  set(FETCHCONTENT_SOURCE_DIR_${contentNameUpper}
+      "${FETCHCONTENT_SOURCE_DIR_${contentNameUpper}}"
+      CACHE PATH "When not empty, overrides where to find pre-populated content for ${contentName}")
+
+  if(FETCHCONTENT_SOURCE_DIR_${contentNameUpper})
+    # The source directory has been explicitly provided in the cache,
+    # so no population is required
+    set(${contentNameLower}_SOURCE_DIR "${FETCHCONTENT_SOURCE_DIR_${contentNameUpper}}")
+    set(${contentNameLower}_BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build")
+
+  elseif(FETCHCONTENT_FULLY_DISCONNECTED)
+    # Bypass population and assume source is already there from a previous run
+    set(${contentNameLower}_SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src")
+    set(${contentNameLower}_BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build")
+
+  else()
+    # Support both a global "disconnect all updates" and a per-content
+    # update test (either one being set disables updates for this content).
+    option(FETCHCONTENT_UPDATES_DISCONNECTED_${contentNameUpper}
+           "Enables UPDATE_DISCONNECTED behavior just for population of ${contentName}")
+    if(FETCHCONTENT_UPDATES_DISCONNECTED OR
+       FETCHCONTENT_UPDATES_DISCONNECTED_${contentNameUpper})
+      set(disconnectUpdates True)
+    else()
+      set(disconnectUpdates False)
+    endif()
+
+    if(FETCHCONTENT_QUIET)
+      set(quietFlag QUIET)
+    else()
+      unset(quietFlag)
+    endif()
+
+    __FetchContent_getSavedDetails(${contentName} contentDetails)
+    if("${contentDetails}" STREQUAL "")
+      message(FATAL_ERROR "No details have been set for content: ${contentName}")
+    endif()
+
+    __FetchContent_directPopulate(
+      ${contentNameLower}
+      ${quietFlag}
+      UPDATE_DISCONNECTED ${disconnectUpdates}
+      SUBBUILD_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-subbuild"
+      SOURCE_DIR   "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src"
+      BINARY_DIR   "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build"
+      # Put the saved details last so they can override any of the
+      # the options we set above (this can include SOURCE_DIR or
+      # BUILD_DIR)
+      ${contentDetails}
+    )
+  endif()
+
+  __FetchContent_setPopulated(
+    ${contentName}
+    ${${contentNameLower}_SOURCE_DIR}
+    ${${contentNameLower}_BINARY_DIR}
+  )
+
+  # Pass variables back to the caller. The variables passed back here
+  # must match what FetchContent_GetProperties() sets when it is called
+  # with just the content name.
+  set(${contentNameLower}_SOURCE_DIR "${${contentNameLower}_SOURCE_DIR}" PARENT_SCOPE)
+  set(${contentNameLower}_BINARY_DIR "${${contentNameLower}_BINARY_DIR}" PARENT_SCOPE)
+  set(${contentNameLower}_POPULATED  True PARENT_SCOPE)
+
+endfunction()
diff --git a/cli/fbank/sys/knf/cmake/Modules/FetchContent/CMakeLists.cmake.in b/cli/fbank/sys/knf/cmake/Modules/FetchContent/CMakeLists.cmake.in
new file mode 100644 (file)
index 0000000..9a7a771
--- /dev/null
@@ -0,0 +1,21 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file Copyright.txt or https://cmake.org/licensing for details.
+
+cmake_minimum_required(VERSION ${CMAKE_VERSION})
+
+# We name the project and the target for the ExternalProject_Add() call
+# to something that will highlight to the user what we are working on if
+# something goes wrong and an error message is produced.
+
+project(${contentName}-populate NONE)
+
+include(ExternalProject)
+ExternalProject_Add(${contentName}-populate
+                    ${ARG_EXTRA}
+                    SOURCE_DIR          "${ARG_SOURCE_DIR}"
+                    BINARY_DIR          "${ARG_BINARY_DIR}"
+                    CONFIGURE_COMMAND   ""
+                    BUILD_COMMAND       ""
+                    INSTALL_COMMAND     ""
+                    TEST_COMMAND        ""
+)
diff --git a/cli/fbank/sys/knf/cmake/Modules/README.md b/cli/fbank/sys/knf/cmake/Modules/README.md
new file mode 100644 (file)
index 0000000..c8d275f
--- /dev/null
@@ -0,0 +1,5 @@
+
+## FetchContent
+
+`FetchContent.cmake` and `FetchContent/CMakeLists.cmake.in`
+are copied from `cmake/3.11.0/share/cmake-3.11/Modules`.
diff --git a/cli/fbank/sys/knf/cmake/__init__.py b/cli/fbank/sys/knf/cmake/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cli/fbank/sys/knf/cmake/cmake_extension.py b/cli/fbank/sys/knf/cmake/cmake_extension.py
new file mode 100644 (file)
index 0000000..f3ca874
--- /dev/null
@@ -0,0 +1,120 @@
+# Copyright (c)  2021  Xiaomi Corporation (author: Fangjun Kuang)
+
+import glob
+import os
+import platform
+import shutil
+import sys
+from pathlib import Path
+
+import setuptools
+from setuptools.command.build_ext import build_ext
+
+
+def is_for_pypi():
+    ans = os.environ.get("KALDI_NATIVE_FBANK_IS_FOR_PYPI", None)
+    return ans is not None
+
+
+def is_macos():
+    return platform.system() == "Darwin"
+
+
+def is_windows():
+    return platform.system() == "Windows"
+
+
+try:
+    from wheel.bdist_wheel import bdist_wheel as _bdist_wheel
+
+    class bdist_wheel(_bdist_wheel):
+        def finalize_options(self):
+            _bdist_wheel.finalize_options(self)
+            # In this case, the generated wheel has a name in the form
+            # kaldifeat-xxx-pyxx-none-any.whl
+            if is_for_pypi() and not is_macos():
+                self.root_is_pure = True
+            else:
+                # The generated wheel has a name ending with
+                # -linux_x86_64.whl
+                self.root_is_pure = False
+
+
+except ImportError:
+    bdist_wheel = None
+
+
+def cmake_extension(name, *args, **kwargs) -> setuptools.Extension:
+    kwargs["language"] = "c++"
+    sources = []
+    return setuptools.Extension(name, sources, *args, **kwargs)
+
+
+class BuildExtension(build_ext):
+    def build_extension(self, ext: setuptools.extension.Extension):
+        # build/temp.linux-x86_64-3.8
+        os.makedirs(self.build_temp, exist_ok=True)
+
+        # build/lib.linux-x86_64-3.8
+        os.makedirs(self.build_lib, exist_ok=True)
+
+        install_dir = Path(self.build_lib).resolve() / "kaldi_native_fbank"
+
+        kaldi_native_fbank_dir = Path(__file__).parent.parent.resolve()
+
+        cmake_args = os.environ.get("KALDI_NATIVE_FBANK_CMAKE_ARGS", "")
+        make_args = os.environ.get("KALDI_NATIVE_FBANK_MAKE_ARGS", "")
+        system_make_args = os.environ.get("MAKEFLAGS", "")
+
+        if cmake_args == "":
+            cmake_args = "-DCMAKE_BUILD_TYPE=Release"
+
+        extra_cmake_args = f" -DCMAKE_INSTALL_PREFIX={install_dir} "
+        extra_cmake_args += " -DKALDI_NATIVE_FBANK_BUILD_TESTS=OFF "
+
+        if "PYTHON_EXECUTABLE" not in cmake_args:
+            print(f"Setting PYTHON_EXECUTABLE to {sys.executable}")
+            cmake_args += f" -DPYTHON_EXECUTABLE={sys.executable}"
+
+        cmake_args += extra_cmake_args
+
+        if is_windows():
+            build_cmd = f"""
+                cmake {cmake_args} -B {self.build_temp} -S {kaldi_native_fbank_dir}
+                cmake --build {self.build_temp} --target install --config Release -- -m
+            """
+            print(f"build command is:\n{build_cmd}")
+            ret = os.system(
+                f"cmake {cmake_args} -B {self.build_temp} -S {kaldi_native_fbank_dir}"
+            )
+            if ret != 0:
+                raise Exception("Failed to configure kaldi_native_fbank")
+
+            ret = os.system(
+                f"cmake --build {self.build_temp} --target install --config Release -- -m"
+            )
+            if ret != 0:
+                raise Exception("Failed to install kaldi_native_fbank")
+        else:
+            if make_args == "" and system_make_args == "":
+                print("For fast compilation, run:")
+                print(
+                    'export KALDI_NATIVE_FBANK_MAKE_ARGS="-j"; python setup.py install'
+                )
+
+            build_cmd = f"""
+                cd {self.build_temp}
+
+                cmake {cmake_args} {kaldi_native_fbank_dir}
+
+                make {make_args} install
+            """
+            print(f"build command is:\n{build_cmd}")
+
+            ret = os.system(build_cmd)
+            if ret != 0:
+                raise Exception(
+                    "\nBuild kaldi-native-fbank failed. Please check the error message.\n"
+                    "You can ask for help by creating an issue on GitHub.\n"
+                    "\nClick:\n\thttps://github.com/csukuangfj/kaldi-native-fbank/issues/new\n"  # noqa
+                )
diff --git a/cli/fbank/sys/knf/cmake/googletest.cmake b/cli/fbank/sys/knf/cmake/googletest.cmake
new file mode 100644 (file)
index 0000000..d7816c2
--- /dev/null
@@ -0,0 +1,79 @@
+function(download_googltest)
+  if(CMAKE_VERSION VERSION_LESS 3.11)
+    list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules)
+  endif()
+
+  include(FetchContent)
+
+  set(googletest_URL  "https://github.com/google/googletest/archive/refs/tags/v1.13.0.tar.gz")
+  set(googletest_URL2 "https://huggingface.co/csukuangfj/k2-cmake-deps/resolve/main/googletest-1.13.0.tar.gz")
+  set(googletest_HASH "SHA256=ad7fdba11ea011c1d925b3289cf4af2c66a352e18d4c7264392fead75e919363")
+
+  # If you don't have access to the Internet,
+  # please pre-download googletest
+  set(possible_file_locations
+    $ENV{HOME}/Downloads/googletest-1.13.0.tar.gz
+    ${PROJECT_SOURCE_DIR}/googletest-1.13.0.tar.gz
+    ${PROJECT_BINARY_DIR}/googletest-1.13.0.tar.gz
+    /tmp/googletest-1.13.0.tar.gz
+    /star-fj/fangjun/download/github/googletest-1.13.0.tar.gz
+  )
+
+  foreach(f IN LISTS possible_file_locations)
+    if(EXISTS ${f})
+      set(googletest_URL  "${f}")
+      file(TO_CMAKE_PATH "${googletest_URL}" googletest_URL)
+      set(googletest_URL2)
+      break()
+    endif()
+  endforeach()
+
+  set(BUILD_GMOCK ON CACHE BOOL "" FORCE)
+  set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
+  set(gtest_disable_pthreads ON CACHE BOOL "" FORCE)
+  set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
+
+  FetchContent_Declare(googletest
+    URL
+      ${googletest_URL}
+      ${googletest_URL2}
+    URL_HASH          ${googletest_HASH}
+  )
+
+  FetchContent_GetProperties(googletest)
+  if(NOT googletest_POPULATED)
+    message(STATUS "Downloading googletest from ${googletest_URL}")
+    FetchContent_Populate(googletest)
+  endif()
+  message(STATUS "googletest is downloaded to ${googletest_SOURCE_DIR}")
+  message(STATUS "googletest's binary dir is ${googletest_BINARY_DIR}")
+
+  if(APPLE)
+    set(CMAKE_MACOSX_RPATH ON) # to solve the following warning on macOS
+  endif()
+  #[==[
+  -- Generating done
+    Policy CMP0042 is not set: MACOSX_RPATH is enabled by default.  Run "cmake
+    --help-policy CMP0042" for policy details.  Use the cmake_policy command to
+    set the policy and suppress this warning.
+
+    MACOSX_RPATH is not specified for the following targets:
+
+      gmock
+      gmock_main
+      gtest
+      gtest_main
+
+  This warning is for project developers.  Use -Wno-dev to suppress it.
+  ]==]
+
+  add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
+
+  target_include_directories(gtest
+    INTERFACE
+      ${googletest_SOURCE_DIR}/googletest/include
+      ${googletest_SOURCE_DIR}/googlemock/include
+  )
+endfunction()
+
+download_googltest()
diff --git a/cli/fbank/sys/knf/cmake/pybind11.cmake b/cli/fbank/sys/knf/cmake/pybind11.cmake
new file mode 100644 (file)
index 0000000..c708b5f
--- /dev/null
@@ -0,0 +1,47 @@
+function(download_pybind11)
+  if(CMAKE_VERSION VERSION_LESS 3.11)
+    list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules)
+  endif()
+
+  include(FetchContent)
+
+  set(pybind11_URL  "https://github.com/pybind/pybind11/archive/refs/tags/v2.10.2.tar.gz")
+  set(pybind11_URL2 "https://huggingface.co/csukuangfj/sherpa-onnx-cmake-deps/resolve/main/pybind11-2.10.2.tar.gz")
+  set(pybind11_HASH "SHA256=93bd1e625e43e03028a3ea7389bba5d3f9f2596abc074b068e70f4ef9b1314ae")
+
+  # If you don't have access to the Internet,
+  # please pre-download pybind11
+  set(possible_file_locations
+    $ENV{HOME}/Downloads/pybind11-2.10.2.tar.gz
+    ${PROJECT_SOURCE_DIR}/pybind11-2.10.2.tar.gz
+    ${PROJECT_BINARY_DIR}/pybind11-2.10.2.tar.gz
+    /tmp/pybind11-2.10.2.tar.gz
+    /star-fj/fangjun/download/github/pybind11-2.10.2.tar.gz
+  )
+
+  foreach(f IN LISTS possible_file_locations)
+    if(EXISTS ${f})
+      set(pybind11_URL  "${f}")
+      file(TO_CMAKE_PATH "${pybind11_URL}" pybind11_URL)
+      set(pybind11_URL2)
+      break()
+    endif()
+  endforeach()
+
+  FetchContent_Declare(pybind11
+    URL
+      ${pybind11_URL}
+      ${pybind11_URL2}
+    URL_HASH          ${pybind11_HASH}
+  )
+
+  FetchContent_GetProperties(pybind11)
+  if(NOT pybind11_POPULATED)
+    message(STATUS "Downloading pybind11 from ${pybind11_URL}")
+    FetchContent_Populate(pybind11)
+  endif()
+  message(STATUS "pybind11 is downloaded to ${pybind11_SOURCE_DIR}")
+  add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR} EXCLUDE_FROM_ALL)
+endfunction()
+
+download_pybind11()
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/CMakeLists.txt b/cli/fbank/sys/knf/kaldi-native-fbank/CMakeLists.txt
new file mode 100644 (file)
index 0000000..2037626
--- /dev/null
@@ -0,0 +1,8 @@
+add_subdirectory(csrc)
+
+if(KALDI_NATIVE_FBANK_BUILD_PYTHON)
+  message(STATUS "Building Python")
+  add_subdirectory(python)
+else()
+  message(STATUS "Disable building Python")
+endif()
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/CMakeLists.txt b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/CMakeLists.txt
new file mode 100644 (file)
index 0000000..69b7da4
--- /dev/null
@@ -0,0 +1,101 @@
+
+include_directories(${PROJECT_SOURCE_DIR})
+set(sources
+  feature-fbank.cc
+  feature-functions.cc
+  feature-mfcc.cc
+  feature-window.cc
+  fftsg.cc
+  kaldi-math.cc
+  mel-computations.cc
+  online-feature.cc
+  rfft.cc
+  whisper-feature.cc
+)
+
+if(KALDI_NATIVE_FBANK_ENABLE_CHECK)
+  list(APPEND sources log.cc)
+endif()
+
+add_library(kaldi-native-fbank-core ${sources})
+if(KALDI_NATIVE_FBANK_ENABLE_CHECK)
+  target_compile_definitions(kaldi-native-fbank-core PUBLIC KNF_ENABLE_CHECK=1)
+
+  if(KNF_HAVE_EXECINFO_H)
+    target_compile_definitions(kaldi-native-fbank-core PRIVATE KNF_HAVE_EXECINFO_H=1)
+  endif()
+
+  if(KNF_HAVE_CXXABI_H)
+    target_compile_definitions(kaldi-native-fbank-core PRIVATE KNF_HAVE_CXXABI_H=1)
+  endif()
+endif()
+
+# We are using std::call_once() in log.h,which requires us to link with -pthread
+if(NOT WIN32 AND KALDI_NATIVE_FBANK_ENABLE_CHECK)
+  target_link_libraries(kaldi-native-fbank-core -pthread)
+endif()
+
+if(KALDI_NATIVE_FBANK_BUILD_TESTS)
+  add_executable(test-online-fbank test-online-fbank.cc)
+  target_link_libraries(test-online-fbank kaldi-native-fbank-core)
+endif()
+
+function(kaldi_native_fbank_add_test source)
+  get_filename_component(name ${source} NAME_WE)
+  add_executable(${name} "${source}")
+  target_link_libraries(${name}
+    PRIVATE
+      kaldi-native-fbank-core
+      gtest
+      gtest_main
+  )
+
+  add_test(NAME "Test.${name}"
+    COMMAND
+    $<TARGET_FILE:${name}>
+  )
+endfunction()
+
+# please sort the source files alphabetically
+set(test_srcs
+  # test-online-feature.cc
+  test-log.cc
+  test-rfft.cc
+)
+
+if(KALDI_NATIVE_FBANK_BUILD_TESTS)
+  foreach(source IN LISTS test_srcs)
+    kaldi_native_fbank_add_test(${source})
+  endforeach()
+endif()
+
+install(TARGETS kaldi-native-fbank-core
+  DESTINATION lib
+)
+if(KALDI_NATIVE_FBANK_BUILD_PYTHON AND WIN32)
+  install(TARGETS kaldi-native-fbank-core
+    DESTINATION ..
+  )
+endif()
+
+if(KALDI_NATIVE_FBANK_BUILD_TESTS)
+  install(TARGETS test-online-fbank
+    DESTINATION bin
+  )
+endif()
+
+file(MAKE_DIRECTORY
+  DESTINATION
+    ${PROJECT_BINARY_DIR}/include/kaldi-native-fbank/csrc
+)
+file(GLOB_RECURSE all_headers *.h)
+
+file(COPY
+  ${all_headers}
+  DESTINATION
+    ${PROJECT_BINARY_DIR}/include/kaldi-native-fbank/csrc
+)
+
+install(FILES ${all_headers}
+  DESTINATION include/kaldi-native-fbank/csrc
+)
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/CPPLINT.cfg b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/CPPLINT.cfg
new file mode 100644 (file)
index 0000000..4c1a8fb
--- /dev/null
@@ -0,0 +1 @@
+exclude_files=fftsg.cc
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-fbank.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-fbank.cc
new file mode 100644 (file)
index 0000000..b73aa80
--- /dev/null
@@ -0,0 +1,116 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file is copied/modified from kaldi/src/feat/feature-fbank.cc
+//
+#include "kaldi-native-fbank/csrc/feature-fbank.h"
+
+#include <algorithm>
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-functions.h"
+#include "kaldi-native-fbank/csrc/kaldi-math.h"
+#include "kaldi-native-fbank/csrc/log.h"
+
+namespace knf {
+
+std::ostream &operator<<(std::ostream &os, const FbankOptions &opts) {
+  os << opts.ToString();
+  return os;
+}
+
+FbankComputer::FbankComputer(const FbankOptions &opts)
+    : opts_(opts), rfft_(opts.frame_opts.PaddedWindowSize()) {
+  if (opts.energy_floor > 0.0f) {
+    log_energy_floor_ = logf(opts.energy_floor);
+  }
+
+  // We'll definitely need the filterbanks info for VTLN warping factor 1.0.
+  // [note: this call caches it.]
+  GetMelBanks(1.0f);
+}
+
+FbankComputer::~FbankComputer() {
+  for (auto iter = mel_banks_.begin(); iter != mel_banks_.end(); ++iter)
+    delete iter->second;
+}
+
+const MelBanks *FbankComputer::GetMelBanks(float vtln_warp) {
+  MelBanks *this_mel_banks = nullptr;
+
+  // std::map<float, MelBanks *>::iterator iter = mel_banks_.find(vtln_warp);
+  auto iter = mel_banks_.find(vtln_warp);
+  if (iter == mel_banks_.end()) {
+    this_mel_banks = new MelBanks(opts_.mel_opts, opts_.frame_opts, vtln_warp);
+    mel_banks_[vtln_warp] = this_mel_banks;
+  } else {
+    this_mel_banks = iter->second;
+  }
+  return this_mel_banks;
+}
+
+void FbankComputer::Compute(float signal_raw_log_energy, float vtln_warp,
+                            std::vector<float> *signal_frame, float *feature) {
+  const MelBanks &mel_banks = *(GetMelBanks(vtln_warp));
+
+  KNF_CHECK_EQ(signal_frame->size(), opts_.frame_opts.PaddedWindowSize());
+
+  // Compute energy after window function (not the raw one).
+  if (opts_.use_energy && !opts_.raw_energy) {
+    signal_raw_log_energy = std::log(
+        std::max<float>(InnerProduct(signal_frame->data(), signal_frame->data(),
+                                     signal_frame->size()),
+                        std::numeric_limits<float>::epsilon()));
+  }
+  rfft_.Compute(signal_frame->data());  // signal_frame is modified in-place
+  ComputePowerSpectrum(signal_frame);
+
+  // Use magnitude instead of power if requested.
+  if (!opts_.use_power) {
+    Sqrt(signal_frame->data(), signal_frame->size() / 2 + 1);
+  }
+
+  int32_t mel_offset = ((opts_.use_energy && !opts_.htk_compat) ? 1 : 0);
+
+  // Its length is opts_.mel_opts.num_bins
+  float *mel_energies = feature + mel_offset;
+
+  // Sum with mel filter banks over the power spectrum
+  mel_banks.Compute(signal_frame->data(), mel_energies);
+
+  if (opts_.use_log_fbank) {
+    // Avoid log of zero (which should be prevented anyway by dithering).
+    for (int32_t i = 0; i != opts_.mel_opts.num_bins; ++i) {
+      auto t = std::max(mel_energies[i], std::numeric_limits<float>::epsilon());
+      mel_energies[i] = std::log(t);
+    }
+  }
+
+  // Copy energy as first value (or the last, if htk_compat == true).
+  if (opts_.use_energy) {
+    if (opts_.energy_floor > 0.0 && signal_raw_log_energy < log_energy_floor_) {
+      signal_raw_log_energy = log_energy_floor_;
+    }
+    int32_t energy_index = opts_.htk_compat ? opts_.mel_opts.num_bins : 0;
+    feature[energy_index] = signal_raw_log_energy;
+  }
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-fbank.h b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-fbank.h
new file mode 100644 (file)
index 0000000..84005e0
--- /dev/null
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file is copied/modified from kaldi/src/feat/feature-fbank.h
+
+#ifndef KALDI_NATIVE_FBANK_CSRC_FEATURE_FBANK_H_
+#define KALDI_NATIVE_FBANK_CSRC_FEATURE_FBANK_H_
+
+#include <map>
+#include <string>
+#include <vector>
+#include <cstdint>
+#include <sstream>
+
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+#include "kaldi-native-fbank/csrc/rfft.h"
+
+namespace knf {
+
+struct FbankOptions {
+  FrameExtractionOptions frame_opts;
+  MelBanksOptions mel_opts;
+  // append an extra dimension with energy to the filter banks
+  bool use_energy = false;
+  float energy_floor = 0.0f;  // active iff use_energy==true
+
+  // If true, compute log_energy before preemphasis and windowing
+  // If false, compute log_energy after preemphasis ans windowing
+  bool raw_energy = true;  // active iff use_energy==true
+
+  // If true, put energy last (if using energy)
+  // If false, put energy first
+  bool htk_compat = false;  // active iff use_energy==true
+
+  // if true (default), produce log-filterbank, else linear
+  bool use_log_fbank = true;
+
+  // if true (default), use power in filterbank
+  // analysis, else magnitude.
+  bool use_power = true;
+
+  FbankOptions() { mel_opts.num_bins = 23; }
+
+  std::string ToString() const {
+    std::ostringstream os;
+    os << "frame_opts: \n";
+    os << frame_opts << "\n";
+    os << "\n";
+
+    os << "mel_opts: \n";
+    os << mel_opts << "\n";
+
+    os << "use_energy: " << use_energy << "\n";
+    os << "energy_floor: " << energy_floor << "\n";
+    os << "raw_energy: " << raw_energy << "\n";
+    os << "htk_compat: " << htk_compat << "\n";
+    os << "use_log_fbank: " << use_log_fbank << "\n";
+    os << "use_power: " << use_power << "\n";
+    return os.str();
+  }
+};
+
+std::ostream &operator<<(std::ostream &os, const FbankOptions &opts);
+
+class FbankComputer {
+ public:
+  using Options = FbankOptions;
+
+  explicit FbankComputer(const FbankOptions &opts);
+  ~FbankComputer();
+
+  int32_t Dim() const {
+    return opts_.mel_opts.num_bins + (opts_.use_energy ? 1 : 0);
+  }
+
+  // if true, compute log_energy_pre_window but after dithering and dc removal
+  bool NeedRawLogEnergy() const { return opts_.use_energy && opts_.raw_energy; }
+
+  const FrameExtractionOptions &GetFrameOptions() const {
+    return opts_.frame_opts;
+  }
+
+  const FbankOptions &GetOptions() const { return opts_; }
+
+  /**
+     Function that computes one frame of features from
+     one frame of signal.
+
+     @param [in] signal_raw_log_energy The log-energy of the frame of the signal
+         prior to windowing and pre-emphasis, or
+         log(numeric_limits<float>::min()), whichever is greater.  Must be
+         ignored by this function if this class returns false from
+         this->NeedsRawLogEnergy().
+     @param [in] vtln_warp  The VTLN warping factor that the user wants
+         to be applied when computing features for this utterance.  Will
+         normally be 1.0, meaning no warping is to be done.  The value will
+         be ignored for feature types that don't support VLTN, such as
+         spectrogram features.
+     @param [in] signal_frame  One frame of the signal,
+       as extracted using the function ExtractWindow() using the options
+       returned by this->GetFrameOptions().  The function will use the
+       vector as a workspace, which is why it's a non-const pointer.
+     @param [out] feature  Pointer to a vector of size this->Dim(), to which
+         the computed feature will be written. It should be pre-allocated.
+  */
+  void Compute(float signal_raw_log_energy, float vtln_warp,
+               std::vector<float> *signal_frame, float *feature);
+
+ private:
+  const MelBanks *GetMelBanks(float vtln_warp);
+
+  FbankOptions opts_;
+  float log_energy_floor_;
+  std::map<float, MelBanks *> mel_banks_;  // float is VTLN coefficient.
+  Rfft rfft_;
+};
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_CSRC_FEATURE_FBANK_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-functions.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-functions.cc
new file mode 100644 (file)
index 0000000..00ae4c7
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file is copied/modified from kaldi/src/feat/feature-functions.cc
+
+#include "kaldi-native-fbank/csrc/feature-functions.h"
+
+#include <cstdint>
+#include <vector>
+
+namespace knf {
+
+void ComputePowerSpectrum(std::vector<float> *complex_fft) {
+  int32_t dim = complex_fft->size();
+
+  // now we have in complex_fft, first half of complex spectrum
+  // it's stored as [real0, realN/2, real1, im1, real2, im2, ...]
+
+  float *p = complex_fft->data();
+  int32_t half_dim = dim / 2;
+  float first_energy = p[0] * p[0];
+  float last_energy = p[1] * p[1];  // handle this special case
+
+  for (int32_t i = 1; i < half_dim; ++i) {
+    float real = p[i * 2];
+    float im = p[i * 2 + 1];
+    p[i] = real * real + im * im;
+  }
+  p[0] = first_energy;
+  p[half_dim] = last_energy;  // Will actually never be used, and anyway
+  // if the signal has been bandlimited sensibly this should be zero.
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-functions.h b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-functions.h
new file mode 100644 (file)
index 0000000..b221622
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file is copied/modified from kaldi/src/feat/feature-functions.h
+#ifndef KALDI_NATIVE_FBANK_CSRC_FEATURE_FUNCTIONS_H_
+#define KALDI_NATIVE_FBANK_CSRC_FEATURE_FUNCTIONS_H_
+
+#include <vector>
+namespace knf {
+
+// ComputePowerSpectrum converts a complex FFT (as produced by the FFT
+// functions in csrc/rfft.h), and converts it into
+// a power spectrum.  If the complex FFT is a vector of size n (representing
+// half of the complex FFT of a real signal of size n, as described there),
+// this function computes in the first (n/2) + 1 elements of it, the
+// energies of the fft bins from zero to the Nyquist frequency.  Contents of the
+// remaining (n/2) - 1 elements are undefined at output.
+
+void ComputePowerSpectrum(std::vector<float> *complex_fft);
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_CSRC_FEATURE_FUNCTIONS_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-mfcc.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-mfcc.cc
new file mode 100644 (file)
index 0000000..6b75285
--- /dev/null
@@ -0,0 +1,174 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file is copied/modified from kaldi/src/feat/feature-mfcc.cc
+//
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-functions.h"
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/kaldi-math.h"
+#include "kaldi-native-fbank/csrc/log.h"
+
+namespace knf {
+
+static std::vector<float> ComputeDctMatrix(int32_t num_rows, int32_t num_cols) {
+  // this function is copied from
+  // https://github.com/kaldi-asr/kaldi/blob/master/src/matrix/matrix-functions.cc#L592
+
+  std::vector<float> ans(num_rows * num_cols);
+  float *p = ans.data();
+
+  float normalizer = std::sqrt(1.0 / num_cols);  // normalizer for X_0
+
+  for (int32_t i = 0; i != num_cols; ++i) {
+    p[i] = normalizer;
+  }
+
+  normalizer = std::sqrt(2.0 / num_cols);  // normalizer for other elements
+
+  for (int32_t k = 1; k != num_rows; ++k) {
+    for (int32_t n = 0; n != num_cols; ++n) {
+      *(p + k * num_cols + n) =
+          normalizer *
+          std::cos(static_cast<double>(M_PI) / num_cols * (n + 0.5) * k);
+    }
+  }
+
+  return ans;
+}
+
+std::ostream &operator<<(std::ostream &os, const MfccOptions &opts) {
+  os << opts.ToString();
+  return os;
+}
+
+MfccComputer::MfccComputer(const MfccOptions &opts)
+    : opts_(opts),
+      rfft_(opts.frame_opts.PaddedWindowSize()),
+      mel_energies_(opts.mel_opts.num_bins) {
+  if (opts.energy_floor > 0.0f) {
+    log_energy_floor_ = logf(opts.energy_floor);
+  }
+
+  // We'll definitely need the filterbanks info for VTLN warping factor 1.0.
+  // [note: this call caches it.]
+  GetMelBanks(1.0f);
+
+  int32_t num_bins = opts.mel_opts.num_bins;
+
+  KNF_CHECK_LE(opts.num_ceps, num_bins)
+      << "num-ceps cannot be larger than num-mel-bins."
+      << " It should be smaller or equal. You provided num-ceps: "
+      << opts.num_ceps << "  and num-mel-bins: " << num_bins;
+
+  dct_matrix_ = ComputeDctMatrix(opts.num_ceps, num_bins);
+
+  if (opts.cepstral_lifter != 0.0) {
+    lifter_coeffs_ = std::vector<float>(opts.num_ceps);
+    // torch::empty({1, opts.num_ceps}, torch::kFloat32);
+    ComputeLifterCoeffs(opts.cepstral_lifter, &lifter_coeffs_);
+  }
+}
+
+MfccComputer::~MfccComputer() {
+  for (auto iter = mel_banks_.begin(); iter != mel_banks_.end(); ++iter)
+    delete iter->second;
+}
+
+const MelBanks *MfccComputer::GetMelBanks(float vtln_warp) {
+  MelBanks *this_mel_banks = nullptr;
+
+  // std::map<float, MelBanks *>::iterator iter = mel_banks_.find(vtln_warp);
+  auto iter = mel_banks_.find(vtln_warp);
+  if (iter == mel_banks_.end()) {
+    this_mel_banks = new MelBanks(opts_.mel_opts, opts_.frame_opts, vtln_warp);
+    mel_banks_[vtln_warp] = this_mel_banks;
+  } else {
+    this_mel_banks = iter->second;
+  }
+  return this_mel_banks;
+}
+
+void MfccComputer::Compute(float signal_raw_log_energy, float vtln_warp,
+                           std::vector<float> *signal_frame, float *feature) {
+  const MelBanks &mel_banks = *(GetMelBanks(vtln_warp));
+
+  KNF_CHECK_EQ(signal_frame->size(), opts_.frame_opts.PaddedWindowSize());
+
+  // Compute energy after window function (not the raw one).
+  if (opts_.use_energy && !opts_.raw_energy) {
+    signal_raw_log_energy = std::log(
+        std::max<float>(InnerProduct(signal_frame->data(), signal_frame->data(),
+                                     signal_frame->size()),
+                        std::numeric_limits<float>::epsilon()));
+  }
+  rfft_.Compute(signal_frame->data());  // signal_frame is modified in-place
+  ComputePowerSpectrum(signal_frame);
+
+  // Sum with mel filter banks over the power spectrum
+  mel_banks.Compute(signal_frame->data(), mel_energies_.data());
+
+  // Avoid log of zero (which should be prevented anyway by dithering).
+  for (int32_t i = 0; i != opts_.mel_opts.num_bins; ++i) {
+    auto t = std::max(mel_energies_[i], std::numeric_limits<float>::epsilon());
+    mel_energies_[i] = std::log(t);
+  }
+
+  // feature = dct_matrix_ * mel_energies [which now have log]
+  for (int32_t i = 0; i != opts_.num_ceps; ++i) {
+    feature[i] = InnerProduct(dct_matrix_.data() + i * opts_.mel_opts.num_bins,
+                              mel_energies_.data(), opts_.mel_opts.num_bins);
+  }
+
+  if (opts_.cepstral_lifter != 0.0) {
+    for (int32_t i = 0; i != opts_.num_ceps; ++i) {
+      feature[i] *= lifter_coeffs_[i];
+    }
+  }
+
+  if (opts_.use_energy) {
+    if (opts_.energy_floor > 0.0 && signal_raw_log_energy < log_energy_floor_) {
+      signal_raw_log_energy = log_energy_floor_;
+    }
+    feature[0] = signal_raw_log_energy;
+  }
+
+  if (opts_.htk_compat) {
+    float energy = feature[0];
+
+    for (int32_t i = 0; i < opts_.num_ceps - 1; ++i) {
+      feature[i] = feature[i + 1];
+    }
+
+    if (!opts_.use_energy) {
+      energy *= M_SQRT2;  // scale on C0 (actually removing a scale
+    }
+    // we previously added that's part of one common definition of
+    // the cosine transform.)
+    feature[opts_.num_ceps - 1] = energy;
+  }
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-mfcc.h b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-mfcc.h
new file mode 100644 (file)
index 0000000..32dc772
--- /dev/null
@@ -0,0 +1,151 @@
+/**
+ * Copyright 2009-2011  Karel Vesely;  Petr Motlicek;  Saarland University
+ *           2014-2016  Johns Hopkins University (author: Daniel Povey)
+ * Copyright 2024       Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file is copied/modified from kaldi/src/feat/feature-mfcc.h
+
+#ifndef KALDI_NATIVE_FBANK_CSRC_FEATURE_MFCC_H_
+#define KALDI_NATIVE_FBANK_CSRC_FEATURE_MFCC_H_
+
+#include <cstdint>
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+#include "kaldi-native-fbank/csrc/rfft.h"
+
+namespace knf {
+
+/// MfccOptions contains basic options for computing MFCC features.
+// (this class is copied from kaldi)
+struct MfccOptions {
+  FrameExtractionOptions frame_opts;
+  MelBanksOptions mel_opts;
+
+  // Number of cepstra in MFCC computation (including C0)
+  int32_t num_ceps = 13;
+
+  // Use energy (not C0) in MFCC computation
+  bool use_energy = true;
+
+  // Floor on energy (absolute, not relative) in MFCC
+  // computation. Only makes a difference if use_energy=true;
+  // only necessary if dither=0.0.
+  // Suggested values: 0.1 or 1.0
+  float energy_floor = 0.0;
+
+  // If true, compute energy before preemphasis and windowing
+  bool raw_energy = true;
+
+  // Constant that controls scaling of MFCCs
+  float cepstral_lifter = 22.0;
+
+  // If true, put energy or C0 last and use a factor of
+  // sqrt(2) on C0.
+  // Warning: not sufficient to get HTK compatible features
+  // (need to change other parameters)
+  bool htk_compat = false;
+
+  MfccOptions() { mel_opts.num_bins = 23; }
+
+  std::string ToString() const {
+    std::ostringstream os;
+    os << "MfccOptions(";
+    os << "frame_opts=" << frame_opts.ToString() << ", ";
+    os << "mel_opts=" << mel_opts.ToString() << ", ";
+
+    os << "num_ceps=" << num_ceps << ", ";
+    os << "use_energy=" << (use_energy ? "True" : "False") << ", ";
+    os << "energy_floor=" << energy_floor << ", ";
+    os << "raw_energy=" << (raw_energy ? "True" : "False") << ", ";
+    os << "cepstral_lifter=" << cepstral_lifter << ", ";
+    os << "htk_compat=" << (htk_compat ? "True" : "False") << ")";
+
+    return os.str();
+  }
+};
+
+std::ostream &operator<<(std::ostream &os, const MfccOptions &opts);
+
+class MfccComputer {
+ public:
+  using Options = MfccOptions;
+
+  explicit MfccComputer(const MfccOptions &opts);
+  ~MfccComputer();
+
+  int32_t Dim() const { return opts_.num_ceps; }
+
+  // if true, compute log_energy_pre_window but after dithering and dc removal
+  bool NeedRawLogEnergy() const { return opts_.use_energy && opts_.raw_energy; }
+
+  const FrameExtractionOptions &GetFrameOptions() const {
+    return opts_.frame_opts;
+  }
+
+  const MfccOptions &GetOptions() const { return opts_; }
+
+  /**
+     Function that computes one frame of features from
+     one frame of signal.
+
+     @param [in] signal_raw_log_energy The log-energy of the frame of the signal
+         prior to windowing and pre-emphasis, or
+         log(numeric_limits<float>::min()), whichever is greater.  Must be
+         ignored by this function if this class returns false from
+         this->NeedsRawLogEnergy().
+     @param [in] vtln_warp  The VTLN warping factor that the user wants
+         to be applied when computing features for this utterance.  Will
+         normally be 1.0, meaning no warping is to be done.  The value will
+         be ignored for feature types that don't support VLTN, such as
+         spectrogram features.
+     @param [in] signal_frame  One frame of the signal,
+       as extracted using the function ExtractWindow() using the options
+       returned by this->GetFrameOptions().  The function will use the
+       vector as a workspace, which is why it's a non-const pointer.
+     @param [out] feature  Pointer to a vector of size this->Dim(), to which
+         the computed feature will be written. It should be pre-allocated.
+  */
+  void Compute(float signal_raw_log_energy, float vtln_warp,
+               std::vector<float> *signal_frame, float *feature);
+
+ private:
+  const MelBanks *GetMelBanks(float vtln_warp);
+
+  MfccOptions opts_;
+  float log_energy_floor_;
+  std::map<float, MelBanks *> mel_banks_;  // float is VTLN coefficient.
+  Rfft rfft_;
+
+  // temp buffer of size num_mel_bins = opts.mel_opts.num_bins
+  std::vector<float> mel_energies_;
+
+  // opts_.num_ceps
+  std::vector<float> lifter_coeffs_;
+
+  // [num_ceps][num_mel_bins]
+  std::vector<float> dct_matrix_;
+};
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_CSRC_FEATURE_MFCC_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-window.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-window.cc
new file mode 100644 (file)
index 0000000..c5d606f
--- /dev/null
@@ -0,0 +1,257 @@
+// kaldi-native-fbank/csrc/feature-window.cc
+//
+// Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+
+// This file is copied/modified from kaldi/src/feat/feature-window.cc
+
+#include "kaldi-native-fbank/csrc/feature-window.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <vector>
+#include <cstddef>
+
+#include "kaldi-native-fbank/csrc/kaldi-math.h"
+
+namespace knf {
+
+std::ostream &operator<<(std::ostream &os, const FrameExtractionOptions &opts) {
+  os << opts.ToString();
+  return os;
+}
+
+FeatureWindowFunction::FeatureWindowFunction(const FrameExtractionOptions &opts)
+    : window_(opts.WindowSize()) {
+  int32_t frame_length = opts.WindowSize();
+  KNF_CHECK_GT(frame_length, 0);
+
+  float *window_data = window_.data();
+
+  double a = M_2PI / (frame_length - 1);
+  if (opts.window_type == "hann") {
+    // see https://pytorch.org/docs/stable/generated/torch.hann_window.html
+    // We assume periodic is true
+    a = M_2PI / frame_length;
+  }
+
+  for (int32_t i = 0; i < frame_length; i++) {
+    double i_fl = static_cast<double>(i);
+    if (opts.window_type == "hanning") {
+      window_data[i] = 0.5 - 0.5 * cos(a * i_fl);
+    } else if (opts.window_type == "sine") {
+      // when you are checking ws wikipedia, please
+      // note that 0.5 * a = M_PI/(frame_length-1)
+      window_data[i] = sin(0.5 * a * i_fl);
+    } else if (opts.window_type == "hamming") {
+      window_data[i] = 0.54 - 0.46 * cos(a * i_fl);
+    } else if (opts.window_type == "hann") {
+      window_data[i] = 0.50 - 0.50 * cos(a * i_fl);
+    } else if (opts.window_type == "povey") {
+      // like hamming but goes to zero at edges.
+      window_data[i] = pow(0.5 - 0.5 * cos(a * i_fl), 0.85);
+    } else if (opts.window_type == "rectangular") {
+      window_data[i] = 1.0;
+    } else if (opts.window_type == "blackman") {
+      window_data[i] = opts.blackman_coeff - 0.5 * cos(a * i_fl) +
+                       (0.5 - opts.blackman_coeff) * cos(2 * a * i_fl);
+    } else {
+      KNF_LOG(FATAL) << "Invalid window type " << opts.window_type;
+    }
+  }
+}
+
+void FeatureWindowFunction::Apply(float *wave) const {
+  int32_t window_size = window_.size();
+  const float *p = window_.data();
+  for (int32_t k = 0; k != window_size; ++k) {
+    wave[k] *= p[k];
+  }
+}
+
+int64_t FirstSampleOfFrame(int32_t frame, const FrameExtractionOptions &opts) {
+  int64_t frame_shift = opts.WindowShift();
+  if (opts.snip_edges) {
+    return frame * frame_shift;
+  } else {
+    int64_t midpoint_of_frame = frame_shift * frame + frame_shift / 2,
+            beginning_of_frame = midpoint_of_frame - opts.WindowSize() / 2;
+    return beginning_of_frame;
+  }
+}
+
+int32_t NumFrames(int64_t num_samples, const FrameExtractionOptions &opts,
+                  bool flush /*= true*/) {
+  int64_t frame_shift = opts.WindowShift();
+  int64_t frame_length = opts.WindowSize();
+  if (opts.snip_edges) {
+    // with --snip-edges=true (the default), we use a HTK-like approach to
+    // determining the number of frames-- all frames have to fit completely into
+    // the waveform, and the first frame begins at sample zero.
+    if (num_samples < frame_length)
+      return 0;
+    else
+      return (1 + ((num_samples - frame_length) / frame_shift));
+    // You can understand the expression above as follows: 'num_samples -
+    // frame_length' is how much room we have to shift the frame within the
+    // waveform; 'frame_shift' is how much we shift it each time; and the ratio
+    // is how many times we can shift it (integer arithmetic rounds down).
+  } else {
+    // if --snip-edges=false, the number of frames is determined by rounding the
+    // (file-length / frame-shift) to the nearest integer.  The point of this
+    // formula is to make the number of frames an obvious and predictable
+    // function of the frame shift and signal length, which makes many
+    // segmentation-related questions simpler.
+    //
+    // Because integer division in C++ rounds toward zero, we add (half the
+    // frame-shift minus epsilon) before dividing, to have the effect of
+    // rounding towards the closest integer.
+    int32_t num_frames = (num_samples + (frame_shift / 2)) / frame_shift;
+
+    if (flush) return num_frames;
+
+    // note: 'end' always means the last plus one, i.e. one past the last.
+    int64_t end_sample_of_last_frame =
+        FirstSampleOfFrame(num_frames - 1, opts) + frame_length;
+
+    // the following code is optimized more for clarity than efficiency.
+    // If flush == false, we can't output frames that extend past the end
+    // of the signal.
+    while (num_frames > 0 && end_sample_of_last_frame > num_samples) {
+      num_frames--;
+      end_sample_of_last_frame -= frame_shift;
+    }
+    return num_frames;
+  }
+}
+
+void ExtractWindow(int64_t sample_offset, const std::vector<float> &wave,
+                   int32_t f, const FrameExtractionOptions &opts,
+                   const FeatureWindowFunction &window_function,
+                   std::vector<float> *window,
+                   float *log_energy_pre_window /*= nullptr*/) {
+  KNF_CHECK(sample_offset >= 0 && wave.size() != 0);
+
+  int32_t frame_length = opts.WindowSize();
+  int32_t frame_length_padded = opts.PaddedWindowSize();
+
+  int64_t num_samples = sample_offset + wave.size();
+  int64_t start_sample = FirstSampleOfFrame(f, opts);
+  int64_t end_sample = start_sample + frame_length;
+
+  if (opts.snip_edges) {
+    KNF_CHECK(start_sample >= sample_offset && end_sample <= num_samples);
+  } else {
+    KNF_CHECK(sample_offset == 0 || start_sample >= sample_offset);
+  }
+
+  if (window->size() != frame_length_padded) {
+    window->resize(frame_length_padded);
+  }
+
+  // wave_start and wave_end are start and end indexes into 'wave', for the
+  // piece of wave that we're trying to extract.
+  int32_t wave_start = int32_t(start_sample - sample_offset);
+  int32_t wave_end = wave_start + frame_length;
+
+  if (wave_start >= 0 && wave_end <= wave.size()) {
+    // the normal case-- no edge effects to consider.
+    std::copy(wave.begin() + wave_start,
+              wave.begin() + wave_start + frame_length, window->data());
+  } else {
+    // Deal with any end effects by reflection, if needed.  This code will only
+    // be reached for about two frames per utterance, so we don't concern
+    // ourselves excessively with efficiency.
+    int32_t wave_dim = wave.size();
+    for (int32_t s = 0; s < frame_length; ++s) {
+      int32_t s_in_wave = s + wave_start;
+      while (s_in_wave < 0 || s_in_wave >= wave_dim) {
+        // reflect around the beginning or end of the wave.
+        // e.g. -1 -> 0, -2 -> 1.
+        // dim -> dim - 1, dim + 1 -> dim - 2.
+        // the code supports repeated reflections, although this
+        // would only be needed in pathological cases.
+        if (s_in_wave < 0)
+          s_in_wave = -s_in_wave - 1;
+        else
+          s_in_wave = 2 * wave_dim - 1 - s_in_wave;
+      }
+      (*window)[s] = wave[s_in_wave];
+    }
+  }
+
+  ProcessWindow(opts, window_function, window->data(), log_energy_pre_window);
+}
+
+static void RemoveDcOffset(float *d, int32_t n) {
+  float sum = 0;
+  for (int32_t i = 0; i != n; ++i) {
+    sum += d[i];
+  }
+
+  float mean = sum / n;
+
+  for (int32_t i = 0; i != n; ++i) {
+    d[i] -= mean;
+  }
+}
+
+float InnerProduct(const float *a, const float *b, int32_t n) {
+  float sum = 0;
+  for (int32_t i = 0; i != n; ++i) {
+    sum += a[i] * b[i];
+  }
+  return sum;
+}
+
+void Dither(float *d, int32_t n, float dither_value) {
+  if (dither_value == 0.0) {
+    return;
+  }
+
+  RandomState rstate;
+  for (int32_t i = 0; i < n; ++i) {
+    d[i] += RandGauss(&rstate) * dither_value;
+  }
+}
+
+static void Preemphasize(float *d, int32_t n, float preemph_coeff) {
+  if (preemph_coeff == 0.0) {
+    return;
+  }
+
+  KNF_CHECK(preemph_coeff >= 0.0 && preemph_coeff <= 1.0);
+
+  for (int32_t i = n - 1; i > 0; --i) {
+    d[i] -= preemph_coeff * d[i - 1];
+  }
+  d[0] -= preemph_coeff * d[0];
+}
+
+void ProcessWindow(const FrameExtractionOptions &opts,
+                   const FeatureWindowFunction &window_function, float *window,
+                   float *log_energy_pre_window /*= nullptr*/) {
+  int32_t frame_length = opts.WindowSize();
+
+  if (opts.dither != 0.0) {
+    Dither(window, frame_length, opts.dither);
+  }
+
+  if (opts.remove_dc_offset) {
+    RemoveDcOffset(window, frame_length);
+  }
+
+  if (log_energy_pre_window != NULL) {
+    float energy = std::max<float>(InnerProduct(window, window, frame_length),
+                                   std::numeric_limits<float>::epsilon());
+    *log_energy_pre_window = std::log(energy);
+  }
+
+  if (opts.preemph_coeff != 0.0) {
+    Preemphasize(window, frame_length, opts.preemph_coeff);
+  }
+
+  window_function.Apply(window);
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-window.h b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/feature-window.h
new file mode 100644 (file)
index 0000000..88b509e
--- /dev/null
@@ -0,0 +1,178 @@
+// kaldi-native-fbank/csrc/feature-window.h
+//
+// Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+
+// This file is copied/modified from kaldi/src/feat/feature-window.h
+
+#ifndef KALDI_NATIVE_FBANK_CSRC_FEATURE_WINDOW_H_
+#define KALDI_NATIVE_FBANK_CSRC_FEATURE_WINDOW_H_
+
+#include <sstream>
+#include <string>
+#include <vector>
+#include <cstdint>
+
+#include "kaldi-native-fbank/csrc/log.h"
+
+namespace knf {
+
+inline int32_t RoundUpToNearestPowerOfTwo(int32_t n) {
+  // copied from kaldi/src/base/kaldi-math.cc
+  KNF_CHECK_GT(n, 0);
+  n--;
+  n |= n >> 1;
+  n |= n >> 2;
+  n |= n >> 4;
+  n |= n >> 8;
+  n |= n >> 16;
+  return n + 1;
+}
+
+struct FrameExtractionOptions {
+  float samp_freq = 16000;
+  float frame_shift_ms = 10.0f;   // in milliseconds.
+  float frame_length_ms = 25.0f;  // in milliseconds.
+
+  float dither = 0.00003f;  // Amount of dithering, 0.0 means no dither.
+                            // Value 0.00003f is equivalent to 1.0 in kaldi.
+
+  float preemph_coeff = 0.97f;        // Preemphasis coefficient.
+  bool remove_dc_offset = true;       // Subtract mean of wave before FFT.
+  std::string window_type = "povey";  // e.g. Hamming window
+  // May be "hamming", "rectangular", "povey", "hanning", "hann", "sine",
+  // "blackman".
+  // "povey" is a window I made to be similar to Hamming but to go to zero at
+  // the edges, it's pow((0.5 - 0.5*cos(n/N*2*pi)), 0.85) I just don't think the
+  // Hamming window makes sense as a windowing function.
+  bool round_to_power_of_two = true;
+  float blackman_coeff = 0.42f;
+  bool snip_edges = true;
+  // bool allow_downsample = false;
+  // bool allow_upsample = false;
+
+  int32_t WindowShift() const {
+    return static_cast<int32_t>(samp_freq * 0.001f * frame_shift_ms);
+  }
+  int32_t WindowSize() const {
+    return static_cast<int32_t>(samp_freq * 0.001f * frame_length_ms);
+  }
+  int32_t PaddedWindowSize() const {
+    return (round_to_power_of_two ? RoundUpToNearestPowerOfTwo(WindowSize())
+                                  : WindowSize());
+  }
+  std::string ToString() const {
+    std::ostringstream os;
+#define KNF_PRINT(x) os << #x << ": " << x << "\n"
+    KNF_PRINT(samp_freq);
+    KNF_PRINT(frame_shift_ms);
+    KNF_PRINT(frame_length_ms);
+    KNF_PRINT(dither);
+    KNF_PRINT(preemph_coeff);
+    KNF_PRINT(remove_dc_offset);
+    KNF_PRINT(window_type);
+    KNF_PRINT(round_to_power_of_two);
+    KNF_PRINT(blackman_coeff);
+    KNF_PRINT(snip_edges);
+    // KNF_PRINT(allow_downsample);
+    // KNF_PRINT(allow_upsample);
+#undef KNF_PRINT
+    return os.str();
+  }
+};
+
+std::ostream &operator<<(std::ostream &os, const FrameExtractionOptions &opts);
+
+class FeatureWindowFunction {
+ public:
+  FeatureWindowFunction() = default;
+  explicit FeatureWindowFunction(const FrameExtractionOptions &opts);
+  /**
+   * @param wave Pointer to a 1-D array of shape [window_size].
+   *             It is modified in-place: wave[i] = wave[i] * window_[i].
+   * @param
+   */
+  void Apply(float *wave) const;
+  const std::vector<float> &GetWindow() const { return window_; }
+
+ private:
+  std::vector<float> window_;  // of size opts.WindowSize()
+};
+
+int64_t FirstSampleOfFrame(int32_t frame, const FrameExtractionOptions &opts);
+
+/**
+   This function returns the number of frames that we can extract from a wave
+   file with the given number of samples in it (assumed to have the same
+   sampling rate as specified in 'opts').
+
+      @param [in] num_samples  The number of samples in the wave file.
+      @param [in] opts     The frame-extraction options class
+
+      @param [in] flush   True if we are asserting that this number of samples
+   is 'all there is', false if we expecting more data to possibly come in.  This
+   only makes a difference to the answer
+   if opts.snip_edges== false.  For offline feature extraction you always want
+   flush == true.  In an online-decoding context, once you know (or decide) that
+   no more data is coming in, you'd call it with flush == true at the end to
+   flush out any remaining data.
+*/
+int32_t NumFrames(int64_t num_samples, const FrameExtractionOptions &opts,
+                  bool flush = true);
+
+/*
+  ExtractWindow() extracts a windowed frame of waveform (possibly with a
+  power-of-two, padded size, depending on the config), including all the
+  processing done by ProcessWindow().
+
+  @param [in] sample_offset  If 'wave' is not the entire waveform, but
+                   part of it to the left has been discarded, then the
+                   number of samples prior to 'wave' that we have
+                   already discarded.  Set this to zero if you are
+                   processing the entire waveform in one piece, or
+                   if you get 'no matching function' compilation
+                   errors when updating the code.
+  @param [in] wave  The waveform
+  @param [in] f     The frame index to be extracted, with
+                    0 <= f < NumFrames(sample_offset + wave.Dim(), opts, true)
+  @param [in] opts  The options class to be used
+  @param [in] window_function  The windowing function, as derived from the
+                    options class.
+  @param [out] window  The windowed, possibly-padded waveform to be
+                     extracted.  Will be resized as needed.
+  @param [out] log_energy_pre_window  If non-NULL, the log-energy of
+                   the signal prior to pre-emphasis and multiplying by
+                   the windowing function will be written to here.
+*/
+void ExtractWindow(int64_t sample_offset, const std::vector<float> &wave,
+                   int32_t f, const FrameExtractionOptions &opts,
+                   const FeatureWindowFunction &window_function,
+                   std::vector<float> *window,
+                   float *log_energy_pre_window = nullptr);
+
+/**
+  This function does all the windowing steps after actually
+  extracting the windowed signal: depending on the
+  configuration, it does dithering, dc offset removal,
+  preemphasis, and multiplication by the windowing function.
+   @param [in] opts  The options class to be used
+   @param [in] window_function  The windowing function-- should have
+                    been initialized using 'opts'.
+   @param [in,out] window  A vector of size opts.WindowSize().  Note:
+      it will typically be a sub-vector of a larger vector of size
+      opts.PaddedWindowSize(), with the remaining samples zero,
+      as the FFT code is more efficient if it operates on data with
+      power-of-two size.
+   @param [out]   log_energy_pre_window If non-NULL, then after dithering and
+      DC offset removal, this function will write to this pointer the log of
+      the total energy (i.e. sum-squared) of the frame.
+ */
+void ProcessWindow(const FrameExtractionOptions &opts,
+                   const FeatureWindowFunction &window_function, float *window,
+                   float *log_energy_pre_window = nullptr);
+
+// Compute the inner product of two vectors
+float InnerProduct(const float *a, const float *b, int32_t n);
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_CSRC_FEATURE_WINDOW_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/fftsg.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/fftsg.cc
new file mode 100644 (file)
index 0000000..158045b
--- /dev/null
@@ -0,0 +1,2883 @@
+/* This file is copied from
+ *
+ * https://www.kurims.kyoto-u.ac.jp/~ooura/fft.html
+ *
+ * Copyright Takuya OOURA, 1996-2001
+ *
+ * You may use, copy, modify and distribute this code for any
+ * purpose (include commercial use) and without fee. Please refer to
+ * this package when you modify this code.
+ */
+/*
+Fast Fourier/Cosine/Sine Transform
+    dimension   :one
+    data length :power of 2
+    decimation  :frequency
+    radix       :split-radix
+    data        :inplace
+    table       :use
+functions
+    cdft: Complex Discrete Fourier Transform
+    rdft: Real Discrete Fourier Transform
+    ddct: Discrete Cosine Transform
+    ddst: Discrete Sine Transform
+    dfct: Cosine Transform of RDFT (Real Symmetric DFT)
+    dfst: Sine Transform of RDFT (Real Anti-symmetric DFT)
+function prototypes
+    void cdft(int, int, double *, int *, double *);
+    void rdft(int, int, double *, int *, double *);
+    void ddct(int, int, double *, int *, double *);
+    void ddst(int, int, double *, int *, double *);
+    void dfct(int, double *, double *, int *, double *);
+    void dfst(int, double *, double *, int *, double *);
+macro definitions
+    USE_CDFT_PTHREADS : default=not defined
+        CDFT_THREADS_BEGIN_N  : must be >= 512, default=8192
+        CDFT_4THREADS_BEGIN_N : must be >= 512, default=65536
+    USE_CDFT_WINTHREADS : default=not defined
+        CDFT_THREADS_BEGIN_N  : must be >= 512, default=32768
+        CDFT_4THREADS_BEGIN_N : must be >= 512, default=524288
+
+
+-------- Complex DFT (Discrete Fourier Transform) --------
+    [definition]
+        <case1>
+            X[k] = sum_j=0^n-1 x[j]*exp(2*pi*i*j*k/n), 0<=k<n
+        <case2>
+            X[k] = sum_j=0^n-1 x[j]*exp(-2*pi*i*j*k/n), 0<=k<n
+        (notes: sum_j=0^n-1 is a summation from j=0 to n-1)
+    [usage]
+        <case1>
+            ip[0] = 0; // first time only
+            cdft(2*n, 1, a, ip, w);
+        <case2>
+            ip[0] = 0; // first time only
+            cdft(2*n, -1, a, ip, w);
+    [parameters]
+        2*n            :data length (int)
+                        n >= 1, n = power of 2
+        a[0...2*n-1]   :input/output data (double *)
+                        input data
+                            a[2*j] = Re(x[j]),
+                            a[2*j+1] = Im(x[j]), 0<=j<n
+                        output data
+                            a[2*k] = Re(X[k]),
+                            a[2*k+1] = Im(X[k]), 0<=k<n
+        ip[0...*]      :work area for bit reversal (int *)
+                        length of ip >= 2+sqrt(n)
+                        strictly,
+                        length of ip >=
+                            2+(1<<(int)(log(n+0.5)/log(2))/2).
+                        ip[0],ip[1] are pointers of the cos/sin table.
+        w[0...n/2-1]   :cos/sin table (double *)
+                        w[],ip[] are initialized if ip[0] == 0.
+    [remark]
+        Inverse of
+            cdft(2*n, -1, a, ip, w);
+        is
+            cdft(2*n, 1, a, ip, w);
+            for (j = 0; j <= 2 * n - 1; j++) {
+                a[j] *= 1.0 / n;
+            }
+        .
+
+
+-------- Real DFT / Inverse of Real DFT --------
+    [definition]
+        <case1> RDFT
+            R[k] = sum_j=0^n-1 a[j]*cos(2*pi*j*k/n), 0<=k<=n/2
+            I[k] = sum_j=0^n-1 a[j]*sin(2*pi*j*k/n), 0<k<n/2
+        <case2> IRDFT (excluding scale)
+            a[k] = (R[0] + R[n/2]*cos(pi*k))/2 +
+                   sum_j=1^n/2-1 R[j]*cos(2*pi*j*k/n) +
+                   sum_j=1^n/2-1 I[j]*sin(2*pi*j*k/n), 0<=k<n
+    [usage]
+        <case1>
+            ip[0] = 0; // first time only
+            rdft(n, 1, a, ip, w);
+        <case2>
+            ip[0] = 0; // first time only
+            rdft(n, -1, a, ip, w);
+    [parameters]
+        n              :data length (int)
+                        n >= 2, n = power of 2
+        a[0...n-1]     :input/output data (double *)
+                        <case1>
+                            output data
+                                a[2*k] = R[k], 0<=k<n/2
+                                a[2*k+1] = I[k], 0<k<n/2
+                                a[1] = R[n/2]
+                        <case2>
+                            input data
+                                a[2*j] = R[j], 0<=j<n/2
+                                a[2*j+1] = I[j], 0<j<n/2
+                                a[1] = R[n/2]
+        ip[0...*]      :work area for bit reversal (int *)
+                        length of ip >= 2+sqrt(n/2)
+                        strictly,
+                        length of ip >=
+                            2+(1<<(int)(log(n/2+0.5)/log(2))/2).
+                        ip[0],ip[1] are pointers of the cos/sin table.
+        w[0...n/2-1]   :cos/sin table (double *)
+                        w[],ip[] are initialized if ip[0] == 0.
+    [remark]
+        Inverse of
+            rdft(n, 1, a, ip, w);
+        is
+            rdft(n, -1, a, ip, w);
+            for (j = 0; j <= n - 1; j++) {
+                a[j] *= 2.0 / n;
+            }
+        .
+
+
+-------- DCT (Discrete Cosine Transform) / Inverse of DCT --------
+    [definition]
+        <case1> IDCT (excluding scale)
+            C[k] = sum_j=0^n-1 a[j]*cos(pi*j*(k+1/2)/n), 0<=k<n
+        <case2> DCT
+            C[k] = sum_j=0^n-1 a[j]*cos(pi*(j+1/2)*k/n), 0<=k<n
+    [usage]
+        <case1>
+            ip[0] = 0; // first time only
+            ddct(n, 1, a, ip, w);
+        <case2>
+            ip[0] = 0; // first time only
+            ddct(n, -1, a, ip, w);
+    [parameters]
+        n              :data length (int)
+                        n >= 2, n = power of 2
+        a[0...n-1]     :input/output data (double *)
+                        output data
+                            a[k] = C[k], 0<=k<n
+        ip[0...*]      :work area for bit reversal (int *)
+                        length of ip >= 2+sqrt(n/2)
+                        strictly,
+                        length of ip >=
+                            2+(1<<(int)(log(n/2+0.5)/log(2))/2).
+                        ip[0],ip[1] are pointers of the cos/sin table.
+        w[0...n*5/4-1] :cos/sin table (double *)
+                        w[],ip[] are initialized if ip[0] == 0.
+    [remark]
+        Inverse of
+            ddct(n, -1, a, ip, w);
+        is
+            a[0] *= 0.5;
+            ddct(n, 1, a, ip, w);
+            for (j = 0; j <= n - 1; j++) {
+                a[j] *= 2.0 / n;
+            }
+        .
+
+
+-------- DST (Discrete Sine Transform) / Inverse of DST --------
+    [definition]
+        <case1> IDST (excluding scale)
+            S[k] = sum_j=1^n A[j]*sin(pi*j*(k+1/2)/n), 0<=k<n
+        <case2> DST
+            S[k] = sum_j=0^n-1 a[j]*sin(pi*(j+1/2)*k/n), 0<k<=n
+    [usage]
+        <case1>
+            ip[0] = 0; // first time only
+            ddst(n, 1, a, ip, w);
+        <case2>
+            ip[0] = 0; // first time only
+            ddst(n, -1, a, ip, w);
+    [parameters]
+        n              :data length (int)
+                        n >= 2, n = power of 2
+        a[0...n-1]     :input/output data (double *)
+                        <case1>
+                            input data
+                                a[j] = A[j], 0<j<n
+                                a[0] = A[n]
+                            output data
+                                a[k] = S[k], 0<=k<n
+                        <case2>
+                            output data
+                                a[k] = S[k], 0<k<n
+                                a[0] = S[n]
+        ip[0...*]      :work area for bit reversal (int *)
+                        length of ip >= 2+sqrt(n/2)
+                        strictly,
+                        length of ip >=
+                            2+(1<<(int)(log(n/2+0.5)/log(2))/2).
+                        ip[0],ip[1] are pointers of the cos/sin table.
+        w[0...n*5/4-1] :cos/sin table (double *)
+                        w[],ip[] are initialized if ip[0] == 0.
+    [remark]
+        Inverse of
+            ddst(n, -1, a, ip, w);
+        is
+            a[0] *= 0.5;
+            ddst(n, 1, a, ip, w);
+            for (j = 0; j <= n - 1; j++) {
+                a[j] *= 2.0 / n;
+            }
+        .
+
+
+-------- Cosine Transform of RDFT (Real Symmetric DFT) --------
+    [definition]
+        C[k] = sum_j=0^n a[j]*cos(pi*j*k/n), 0<=k<=n
+    [usage]
+        ip[0] = 0; // first time only
+        dfct(n, a, t, ip, w);
+    [parameters]
+        n              :data length - 1 (int)
+                        n >= 2, n = power of 2
+        a[0...n]       :input/output data (double *)
+                        output data
+                            a[k] = C[k], 0<=k<=n
+        t[0...n/2]     :work area (double *)
+        ip[0...*]      :work area for bit reversal (int *)
+                        length of ip >= 2+sqrt(n/4)
+                        strictly,
+                        length of ip >=
+                            2+(1<<(int)(log(n/4+0.5)/log(2))/2).
+                        ip[0],ip[1] are pointers of the cos/sin table.
+        w[0...n*5/8-1] :cos/sin table (double *)
+                        w[],ip[] are initialized if ip[0] == 0.
+    [remark]
+        Inverse of
+            a[0] *= 0.5;
+            a[n] *= 0.5;
+            dfct(n, a, t, ip, w);
+        is
+            a[0] *= 0.5;
+            a[n] *= 0.5;
+            dfct(n, a, t, ip, w);
+            for (j = 0; j <= n; j++) {
+                a[j] *= 2.0 / n;
+            }
+        .
+
+
+-------- Sine Transform of RDFT (Real Anti-symmetric DFT) --------
+    [definition]
+        S[k] = sum_j=1^n-1 a[j]*sin(pi*j*k/n), 0<k<n
+    [usage]
+        ip[0] = 0; // first time only
+        dfst(n, a, t, ip, w);
+    [parameters]
+        n              :data length + 1 (int)
+                        n >= 2, n = power of 2
+        a[0...n-1]     :input/output data (double *)
+                        output data
+                            a[k] = S[k], 0<k<n
+                        (a[0] is used for work area)
+        t[0...n/2-1]   :work area (double *)
+        ip[0...*]      :work area for bit reversal (int *)
+                        length of ip >= 2+sqrt(n/4)
+                        strictly,
+                        length of ip >=
+                            2+(1<<(int)(log(n/4+0.5)/log(2))/2).
+                        ip[0],ip[1] are pointers of the cos/sin table.
+        w[0...n*5/8-1] :cos/sin table (double *)
+                        w[],ip[] are initialized if ip[0] == 0.
+    [remark]
+        Inverse of
+            dfst(n, a, t, ip, w);
+        is
+            dfst(n, a, t, ip, w);
+            for (j = 1; j <= n - 1; j++) {
+                a[j] *= 2.0 / n;
+            }
+        .
+
+
+Appendix :
+    The cos/sin table is recalculated when the larger table required.
+    w[] and ip[] are compatible with all routines.
+*/
+
+#include <math.h>
+
+#ifdef USE_CDFT_PTHREADS
+#define USE_CDFT_THREADS
+#ifndef CDFT_THREADS_BEGIN_N
+#define CDFT_THREADS_BEGIN_N 8192
+#endif
+#ifndef CDFT_4THREADS_BEGIN_N
+#define CDFT_4THREADS_BEGIN_N 65536
+#endif
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#define cdft_thread_t pthread_t
+#define cdft_thread_create(thp,func,argp) { \
+    if (pthread_create(thp, NULL, func, (void *) argp) != 0) { \
+        fprintf(stderr, "cdft thread error\n"); \
+        exit(1); \
+    } \
+}
+#define cdft_thread_wait(th) { \
+    if (pthread_join(th, NULL) != 0) { \
+        fprintf(stderr, "cdft thread error\n"); \
+        exit(1); \
+    } \
+}
+#endif /* USE_CDFT_PTHREADS */
+
+
+#ifdef USE_CDFT_WINTHREADS
+#define USE_CDFT_THREADS
+#ifndef CDFT_THREADS_BEGIN_N
+#define CDFT_THREADS_BEGIN_N 32768
+#endif
+#ifndef CDFT_4THREADS_BEGIN_N
+#define CDFT_4THREADS_BEGIN_N 524288
+#endif
+#include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#define cdft_thread_t HANDLE
+#define cdft_thread_create(thp,func,argp) { \
+    DWORD thid; \
+    *(thp) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) func, (LPVOID) argp, 0, &thid); \
+    if (*(thp) == 0) { \
+        fprintf(stderr, "cdft thread error\n"); \
+        exit(1); \
+    } \
+}
+#define cdft_thread_wait(th) { \
+    WaitForSingleObject(th, INFINITE); \
+    CloseHandle(th); \
+}
+#endif /* USE_CDFT_WINTHREADS */
+
+
+namespace knf {
+
+static void makeipt(int nw, int *ip)
+{
+    int j, l, m, m2, p, q;
+
+    ip[2] = 0;
+    ip[3] = 16;
+    m = 2;
+    for (l = nw; l > 32; l >>= 2) {
+        m2 = m << 1;
+        q = m2 << 3;
+        for (j = m; j < m2; j++) {
+            p = ip[j] << 2;
+            ip[m + j] = p;
+            ip[m2 + j] = p + q;
+        }
+        m = m2;
+    }
+}
+
+static void makewt(int nw, int *ip, double *w)
+{
+    int j, nwh, nw0, nw1;
+    double delta, wn4r, wk1r, wk1i, wk3r, wk3i;
+
+    ip[0] = nw;
+    ip[1] = 1;
+    if (nw > 2) {
+        nwh = nw >> 1;
+        delta = atan(1.0) / nwh;
+        wn4r = cos(delta * nwh);
+        w[0] = 1;
+        w[1] = wn4r;
+        if (nwh == 4) {
+            w[2] = cos(delta * 2);
+            w[3] = sin(delta * 2);
+        } else if (nwh > 4) {
+            makeipt(nw, ip);
+            w[2] = 0.5 / cos(delta * 2);
+            w[3] = 0.5 / cos(delta * 6);
+            for (j = 4; j < nwh; j += 4) {
+                w[j] = cos(delta * j);
+                w[j + 1] = sin(delta * j);
+                w[j + 2] = cos(3 * delta * j);
+                w[j + 3] = -sin(3 * delta * j);
+            }
+        }
+        nw0 = 0;
+        while (nwh > 2) {
+            nw1 = nw0 + nwh;
+            nwh >>= 1;
+            w[nw1] = 1;
+            w[nw1 + 1] = wn4r;
+            if (nwh == 4) {
+                wk1r = w[nw0 + 4];
+                wk1i = w[nw0 + 5];
+                w[nw1 + 2] = wk1r;
+                w[nw1 + 3] = wk1i;
+            } else if (nwh > 4) {
+                wk1r = w[nw0 + 4];
+                wk3r = w[nw0 + 6];
+                w[nw1 + 2] = 0.5 / wk1r;
+                w[nw1 + 3] = 0.5 / wk3r;
+                for (j = 4; j < nwh; j += 4) {
+                    wk1r = w[nw0 + 2 * j];
+                    wk1i = w[nw0 + 2 * j + 1];
+                    wk3r = w[nw0 + 2 * j + 2];
+                    wk3i = w[nw0 + 2 * j + 3];
+                    w[nw1 + j] = wk1r;
+                    w[nw1 + j + 1] = wk1i;
+                    w[nw1 + j + 2] = wk3r;
+                    w[nw1 + j + 3] = wk3i;
+                }
+            }
+            nw0 = nw1;
+        }
+    }
+}
+
+static void makect(int nc, int *ip, double *c)
+{
+    int j, nch;
+    double delta;
+
+    ip[1] = nc;
+    if (nc > 1) {
+        nch = nc >> 1;
+        delta = atan(1.0) / nch;
+        c[0] = cos(delta * nch);
+        c[nch] = 0.5 * c[0];
+        for (j = 1; j < nch; j++) {
+            c[j] = 0.5 * cos(delta * j);
+            c[nc - j] = 0.5 * sin(delta * j);
+        }
+    }
+}
+
+/* -------- child routines -------- */
+
+static void bitrv2(int n, int *ip, double *a)
+{
+    int j, j1, k, k1, l, m, nh, nm;
+    double xr, xi, yr, yi;
+
+    m = 1;
+    for (l = n >> 2; l > 8; l >>= 2) {
+        m <<= 1;
+    }
+    nh = n >> 1;
+    nm = 4 * m;
+    if (l == 8) {
+        for (k = 0; k < m; k++) {
+            for (j = 0; j < k; j++) {
+                j1 = 4 * j + 2 * ip[m + k];
+                k1 = 4 * k + 2 * ip[m + j];
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += 2 * nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 -= nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += 2 * nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nh;
+                k1 += 2;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= 2 * nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 += nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= 2 * nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += 2;
+                k1 += nh;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += 2 * nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 -= nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += 2 * nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nh;
+                k1 -= 2;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= 2 * nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 += nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= 2 * nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+            }
+            k1 = 4 * k + 2 * ip[m + k];
+            j1 = k1 + 2;
+            k1 += nh;
+            xr = a[j1];
+            xi = a[j1 + 1];
+            yr = a[k1];
+            yi = a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            j1 += nm;
+            k1 += 2 * nm;
+            xr = a[j1];
+            xi = a[j1 + 1];
+            yr = a[k1];
+            yi = a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            j1 += nm;
+            k1 -= nm;
+            xr = a[j1];
+            xi = a[j1 + 1];
+            yr = a[k1];
+            yi = a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            j1 -= 2;
+            k1 -= nh;
+            xr = a[j1];
+            xi = a[j1 + 1];
+            yr = a[k1];
+            yi = a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            j1 += nh + 2;
+            k1 += nh + 2;
+            xr = a[j1];
+            xi = a[j1 + 1];
+            yr = a[k1];
+            yi = a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            j1 -= nh - nm;
+            k1 += 2 * nm - 2;
+            xr = a[j1];
+            xi = a[j1 + 1];
+            yr = a[k1];
+            yi = a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+        }
+    } else {
+        for (k = 0; k < m; k++) {
+            for (j = 0; j < k; j++) {
+                j1 = 4 * j + ip[m + k];
+                k1 = 4 * k + ip[m + j];
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nh;
+                k1 += 2;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += 2;
+                k1 += nh;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nh;
+                k1 -= 2;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= nm;
+                xr = a[j1];
+                xi = a[j1 + 1];
+                yr = a[k1];
+                yi = a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+            }
+            k1 = 4 * k + ip[m + k];
+            j1 = k1 + 2;
+            k1 += nh;
+            xr = a[j1];
+            xi = a[j1 + 1];
+            yr = a[k1];
+            yi = a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            j1 += nm;
+            k1 += nm;
+            xr = a[j1];
+            xi = a[j1 + 1];
+            yr = a[k1];
+            yi = a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+        }
+    }
+}
+
+static void bitrv2conj(int n, int *ip, double *a)
+{
+    int j, j1, k, k1, l, m, nh, nm;
+    double xr, xi, yr, yi;
+
+    m = 1;
+    for (l = n >> 2; l > 8; l >>= 2) {
+        m <<= 1;
+    }
+    nh = n >> 1;
+    nm = 4 * m;
+    if (l == 8) {
+        for (k = 0; k < m; k++) {
+            for (j = 0; j < k; j++) {
+                j1 = 4 * j + 2 * ip[m + k];
+                k1 = 4 * k + 2 * ip[m + j];
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += 2 * nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 -= nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += 2 * nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nh;
+                k1 += 2;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= 2 * nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 += nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= 2 * nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += 2;
+                k1 += nh;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += 2 * nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 -= nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += 2 * nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nh;
+                k1 -= 2;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= 2 * nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 += nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= 2 * nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+            }
+            k1 = 4 * k + 2 * ip[m + k];
+            j1 = k1 + 2;
+            k1 += nh;
+            a[j1 - 1] = -a[j1 - 1];
+            xr = a[j1];
+            xi = -a[j1 + 1];
+            yr = a[k1];
+            yi = -a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            a[k1 + 3] = -a[k1 + 3];
+            j1 += nm;
+            k1 += 2 * nm;
+            xr = a[j1];
+            xi = -a[j1 + 1];
+            yr = a[k1];
+            yi = -a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            j1 += nm;
+            k1 -= nm;
+            xr = a[j1];
+            xi = -a[j1 + 1];
+            yr = a[k1];
+            yi = -a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            j1 -= 2;
+            k1 -= nh;
+            xr = a[j1];
+            xi = -a[j1 + 1];
+            yr = a[k1];
+            yi = -a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            j1 += nh + 2;
+            k1 += nh + 2;
+            xr = a[j1];
+            xi = -a[j1 + 1];
+            yr = a[k1];
+            yi = -a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            j1 -= nh - nm;
+            k1 += 2 * nm - 2;
+            a[j1 - 1] = -a[j1 - 1];
+            xr = a[j1];
+            xi = -a[j1 + 1];
+            yr = a[k1];
+            yi = -a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            a[k1 + 3] = -a[k1 + 3];
+        }
+    } else {
+        for (k = 0; k < m; k++) {
+            for (j = 0; j < k; j++) {
+                j1 = 4 * j + ip[m + k];
+                k1 = 4 * k + ip[m + j];
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nh;
+                k1 += 2;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += 2;
+                k1 += nh;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 += nm;
+                k1 += nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nh;
+                k1 -= 2;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+                j1 -= nm;
+                k1 -= nm;
+                xr = a[j1];
+                xi = -a[j1 + 1];
+                yr = a[k1];
+                yi = -a[k1 + 1];
+                a[j1] = yr;
+                a[j1 + 1] = yi;
+                a[k1] = xr;
+                a[k1 + 1] = xi;
+            }
+            k1 = 4 * k + ip[m + k];
+            j1 = k1 + 2;
+            k1 += nh;
+            a[j1 - 1] = -a[j1 - 1];
+            xr = a[j1];
+            xi = -a[j1 + 1];
+            yr = a[k1];
+            yi = -a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            a[k1 + 3] = -a[k1 + 3];
+            j1 += nm;
+            k1 += nm;
+            a[j1 - 1] = -a[j1 - 1];
+            xr = a[j1];
+            xi = -a[j1 + 1];
+            yr = a[k1];
+            yi = -a[k1 + 1];
+            a[j1] = yr;
+            a[j1 + 1] = yi;
+            a[k1] = xr;
+            a[k1 + 1] = xi;
+            a[k1 + 3] = -a[k1 + 3];
+        }
+    }
+}
+
+static void bitrv216(double *a)
+{
+    double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i,
+        x5r, x5i, x7r, x7i, x8r, x8i, x10r, x10i,
+        x11r, x11i, x12r, x12i, x13r, x13i, x14r, x14i;
+
+    x1r = a[2];
+    x1i = a[3];
+    x2r = a[4];
+    x2i = a[5];
+    x3r = a[6];
+    x3i = a[7];
+    x4r = a[8];
+    x4i = a[9];
+    x5r = a[10];
+    x5i = a[11];
+    x7r = a[14];
+    x7i = a[15];
+    x8r = a[16];
+    x8i = a[17];
+    x10r = a[20];
+    x10i = a[21];
+    x11r = a[22];
+    x11i = a[23];
+    x12r = a[24];
+    x12i = a[25];
+    x13r = a[26];
+    x13i = a[27];
+    x14r = a[28];
+    x14i = a[29];
+    a[2] = x8r;
+    a[3] = x8i;
+    a[4] = x4r;
+    a[5] = x4i;
+    a[6] = x12r;
+    a[7] = x12i;
+    a[8] = x2r;
+    a[9] = x2i;
+    a[10] = x10r;
+    a[11] = x10i;
+    a[14] = x14r;
+    a[15] = x14i;
+    a[16] = x1r;
+    a[17] = x1i;
+    a[20] = x5r;
+    a[21] = x5i;
+    a[22] = x13r;
+    a[23] = x13i;
+    a[24] = x3r;
+    a[25] = x3i;
+    a[26] = x11r;
+    a[27] = x11i;
+    a[28] = x7r;
+    a[29] = x7i;
+}
+
+static void bitrv216neg(double *a)
+{
+    double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i,
+        x5r, x5i, x6r, x6i, x7r, x7i, x8r, x8i,
+        x9r, x9i, x10r, x10i, x11r, x11i, x12r, x12i,
+        x13r, x13i, x14r, x14i, x15r, x15i;
+
+    x1r = a[2];
+    x1i = a[3];
+    x2r = a[4];
+    x2i = a[5];
+    x3r = a[6];
+    x3i = a[7];
+    x4r = a[8];
+    x4i = a[9];
+    x5r = a[10];
+    x5i = a[11];
+    x6r = a[12];
+    x6i = a[13];
+    x7r = a[14];
+    x7i = a[15];
+    x8r = a[16];
+    x8i = a[17];
+    x9r = a[18];
+    x9i = a[19];
+    x10r = a[20];
+    x10i = a[21];
+    x11r = a[22];
+    x11i = a[23];
+    x12r = a[24];
+    x12i = a[25];
+    x13r = a[26];
+    x13i = a[27];
+    x14r = a[28];
+    x14i = a[29];
+    x15r = a[30];
+    x15i = a[31];
+    a[2] = x15r;
+    a[3] = x15i;
+    a[4] = x7r;
+    a[5] = x7i;
+    a[6] = x11r;
+    a[7] = x11i;
+    a[8] = x3r;
+    a[9] = x3i;
+    a[10] = x13r;
+    a[11] = x13i;
+    a[12] = x5r;
+    a[13] = x5i;
+    a[14] = x9r;
+    a[15] = x9i;
+    a[16] = x1r;
+    a[17] = x1i;
+    a[18] = x14r;
+    a[19] = x14i;
+    a[20] = x6r;
+    a[21] = x6i;
+    a[22] = x10r;
+    a[23] = x10i;
+    a[24] = x2r;
+    a[25] = x2i;
+    a[26] = x12r;
+    a[27] = x12i;
+    a[28] = x4r;
+    a[29] = x4i;
+    a[30] = x8r;
+    a[31] = x8i;
+}
+
+static void bitrv208(double *a)
+{
+    double x1r, x1i, x3r, x3i, x4r, x4i, x6r, x6i;
+
+    x1r = a[2];
+    x1i = a[3];
+    x3r = a[6];
+    x3i = a[7];
+    x4r = a[8];
+    x4i = a[9];
+    x6r = a[12];
+    x6i = a[13];
+    a[2] = x4r;
+    a[3] = x4i;
+    a[6] = x6r;
+    a[7] = x6i;
+    a[8] = x1r;
+    a[9] = x1i;
+    a[12] = x3r;
+    a[13] = x3i;
+}
+
+static void bitrv208neg(double *a)
+{
+    double x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i,
+        x5r, x5i, x6r, x6i, x7r, x7i;
+
+    x1r = a[2];
+    x1i = a[3];
+    x2r = a[4];
+    x2i = a[5];
+    x3r = a[6];
+    x3i = a[7];
+    x4r = a[8];
+    x4i = a[9];
+    x5r = a[10];
+    x5i = a[11];
+    x6r = a[12];
+    x6i = a[13];
+    x7r = a[14];
+    x7i = a[15];
+    a[2] = x7r;
+    a[3] = x7i;
+    a[4] = x3r;
+    a[5] = x3i;
+    a[6] = x5r;
+    a[7] = x5i;
+    a[8] = x1r;
+    a[9] = x1i;
+    a[10] = x6r;
+    a[11] = x6i;
+    a[12] = x2r;
+    a[13] = x2i;
+    a[14] = x4r;
+    a[15] = x4i;
+}
+
+static void cftf1st(int n, double *a, double *w)
+{
+    int j, j0, j1, j2, j3, k, m, mh;
+    double wn4r, csc1, csc3, wk1r, wk1i, wk3r, wk3i,
+        wd1r, wd1i, wd3r, wd3i;
+    double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i,
+        y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i;
+
+    mh = n >> 3;
+    m = 2 * mh;
+    j1 = m;
+    j2 = j1 + m;
+    j3 = j2 + m;
+    x0r = a[0] + a[j2];
+    x0i = a[1] + a[j2 + 1];
+    x1r = a[0] - a[j2];
+    x1i = a[1] - a[j2 + 1];
+    x2r = a[j1] + a[j3];
+    x2i = a[j1 + 1] + a[j3 + 1];
+    x3r = a[j1] - a[j3];
+    x3i = a[j1 + 1] - a[j3 + 1];
+    a[0] = x0r + x2r;
+    a[1] = x0i + x2i;
+    a[j1] = x0r - x2r;
+    a[j1 + 1] = x0i - x2i;
+    a[j2] = x1r - x3i;
+    a[j2 + 1] = x1i + x3r;
+    a[j3] = x1r + x3i;
+    a[j3 + 1] = x1i - x3r;
+    wn4r = w[1];
+    csc1 = w[2];
+    csc3 = w[3];
+    wd1r = 1;
+    wd1i = 0;
+    wd3r = 1;
+    wd3i = 0;
+    k = 0;
+    for (j = 2; j < mh - 2; j += 4) {
+        k += 4;
+        wk1r = csc1 * (wd1r + w[k]);
+        wk1i = csc1 * (wd1i + w[k + 1]);
+        wk3r = csc3 * (wd3r + w[k + 2]);
+        wk3i = csc3 * (wd3i + w[k + 3]);
+        wd1r = w[k];
+        wd1i = w[k + 1];
+        wd3r = w[k + 2];
+        wd3i = w[k + 3];
+        j1 = j + m;
+        j2 = j1 + m;
+        j3 = j2 + m;
+        x0r = a[j] + a[j2];
+        x0i = a[j + 1] + a[j2 + 1];
+        x1r = a[j] - a[j2];
+        x1i = a[j + 1] - a[j2 + 1];
+        y0r = a[j + 2] + a[j2 + 2];
+        y0i = a[j + 3] + a[j2 + 3];
+        y1r = a[j + 2] - a[j2 + 2];
+        y1i = a[j + 3] - a[j2 + 3];
+        x2r = a[j1] + a[j3];
+        x2i = a[j1 + 1] + a[j3 + 1];
+        x3r = a[j1] - a[j3];
+        x3i = a[j1 + 1] - a[j3 + 1];
+        y2r = a[j1 + 2] + a[j3 + 2];
+        y2i = a[j1 + 3] + a[j3 + 3];
+        y3r = a[j1 + 2] - a[j3 + 2];
+        y3i = a[j1 + 3] - a[j3 + 3];
+        a[j] = x0r + x2r;
+        a[j + 1] = x0i + x2i;
+        a[j + 2] = y0r + y2r;
+        a[j + 3] = y0i + y2i;
+        a[j1] = x0r - x2r;
+        a[j1 + 1] = x0i - x2i;
+        a[j1 + 2] = y0r - y2r;
+        a[j1 + 3] = y0i - y2i;
+        x0r = x1r - x3i;
+        x0i = x1i + x3r;
+        a[j2] = wk1r * x0r - wk1i * x0i;
+        a[j2 + 1] = wk1r * x0i + wk1i * x0r;
+        x0r = y1r - y3i;
+        x0i = y1i + y3r;
+        a[j2 + 2] = wd1r * x0r - wd1i * x0i;
+        a[j2 + 3] = wd1r * x0i + wd1i * x0r;
+        x0r = x1r + x3i;
+        x0i = x1i - x3r;
+        a[j3] = wk3r * x0r + wk3i * x0i;
+        a[j3 + 1] = wk3r * x0i - wk3i * x0r;
+        x0r = y1r + y3i;
+        x0i = y1i - y3r;
+        a[j3 + 2] = wd3r * x0r + wd3i * x0i;
+        a[j3 + 3] = wd3r * x0i - wd3i * x0r;
+        j0 = m - j;
+        j1 = j0 + m;
+        j2 = j1 + m;
+        j3 = j2 + m;
+        x0r = a[j0] + a[j2];
+        x0i = a[j0 + 1] + a[j2 + 1];
+        x1r = a[j0] - a[j2];
+        x1i = a[j0 + 1] - a[j2 + 1];
+        y0r = a[j0 - 2] + a[j2 - 2];
+        y0i = a[j0 - 1] + a[j2 - 1];
+        y1r = a[j0 - 2] - a[j2 - 2];
+        y1i = a[j0 - 1] - a[j2 - 1];
+        x2r = a[j1] + a[j3];
+        x2i = a[j1 + 1] + a[j3 + 1];
+        x3r = a[j1] - a[j3];
+        x3i = a[j1 + 1] - a[j3 + 1];
+        y2r = a[j1 - 2] + a[j3 - 2];
+        y2i = a[j1 - 1] + a[j3 - 1];
+        y3r = a[j1 - 2] - a[j3 - 2];
+        y3i = a[j1 - 1] - a[j3 - 1];
+        a[j0] = x0r + x2r;
+        a[j0 + 1] = x0i + x2i;
+        a[j0 - 2] = y0r + y2r;
+        a[j0 - 1] = y0i + y2i;
+        a[j1] = x0r - x2r;
+        a[j1 + 1] = x0i - x2i;
+        a[j1 - 2] = y0r - y2r;
+        a[j1 - 1] = y0i - y2i;
+        x0r = x1r - x3i;
+        x0i = x1i + x3r;
+        a[j2] = wk1i * x0r - wk1r * x0i;
+        a[j2 + 1] = wk1i * x0i + wk1r * x0r;
+        x0r = y1r - y3i;
+        x0i = y1i + y3r;
+        a[j2 - 2] = wd1i * x0r - wd1r * x0i;
+        a[j2 - 1] = wd1i * x0i + wd1r * x0r;
+        x0r = x1r + x3i;
+        x0i = x1i - x3r;
+        a[j3] = wk3i * x0r + wk3r * x0i;
+        a[j3 + 1] = wk3i * x0i - wk3r * x0r;
+        x0r = y1r + y3i;
+        x0i = y1i - y3r;
+        a[j3 - 2] = wd3i * x0r + wd3r * x0i;
+        a[j3 - 1] = wd3i * x0i - wd3r * x0r;
+    }
+    wk1r = csc1 * (wd1r + wn4r);
+    wk1i = csc1 * (wd1i + wn4r);
+    wk3r = csc3 * (wd3r - wn4r);
+    wk3i = csc3 * (wd3i - wn4r);
+    j0 = mh;
+    j1 = j0 + m;
+    j2 = j1 + m;
+    j3 = j2 + m;
+    x0r = a[j0 - 2] + a[j2 - 2];
+    x0i = a[j0 - 1] + a[j2 - 1];
+    x1r = a[j0 - 2] - a[j2 - 2];
+    x1i = a[j0 - 1] - a[j2 - 1];
+    x2r = a[j1 - 2] + a[j3 - 2];
+    x2i = a[j1 - 1] + a[j3 - 1];
+    x3r = a[j1 - 2] - a[j3 - 2];
+    x3i = a[j1 - 1] - a[j3 - 1];
+    a[j0 - 2] = x0r + x2r;
+    a[j0 - 1] = x0i + x2i;
+    a[j1 - 2] = x0r - x2r;
+    a[j1 - 1] = x0i - x2i;
+    x0r = x1r - x3i;
+    x0i = x1i + x3r;
+    a[j2 - 2] = wk1r * x0r - wk1i * x0i;
+    a[j2 - 1] = wk1r * x0i + wk1i * x0r;
+    x0r = x1r + x3i;
+    x0i = x1i - x3r;
+    a[j3 - 2] = wk3r * x0r + wk3i * x0i;
+    a[j3 - 1] = wk3r * x0i - wk3i * x0r;
+    x0r = a[j0] + a[j2];
+    x0i = a[j0 + 1] + a[j2 + 1];
+    x1r = a[j0] - a[j2];
+    x1i = a[j0 + 1] - a[j2 + 1];
+    x2r = a[j1] + a[j3];
+    x2i = a[j1 + 1] + a[j3 + 1];
+    x3r = a[j1] - a[j3];
+    x3i = a[j1 + 1] - a[j3 + 1];
+    a[j0] = x0r + x2r;
+    a[j0 + 1] = x0i + x2i;
+    a[j1] = x0r - x2r;
+    a[j1 + 1] = x0i - x2i;
+    x0r = x1r - x3i;
+    x0i = x1i + x3r;
+    a[j2] = wn4r * (x0r - x0i);
+    a[j2 + 1] = wn4r * (x0i + x0r);
+    x0r = x1r + x3i;
+    x0i = x1i - x3r;
+    a[j3] = -wn4r * (x0r + x0i);
+    a[j3 + 1] = -wn4r * (x0i - x0r);
+    x0r = a[j0 + 2] + a[j2 + 2];
+    x0i = a[j0 + 3] + a[j2 + 3];
+    x1r = a[j0 + 2] - a[j2 + 2];
+    x1i = a[j0 + 3] - a[j2 + 3];
+    x2r = a[j1 + 2] + a[j3 + 2];
+    x2i = a[j1 + 3] + a[j3 + 3];
+    x3r = a[j1 + 2] - a[j3 + 2];
+    x3i = a[j1 + 3] - a[j3 + 3];
+    a[j0 + 2] = x0r + x2r;
+    a[j0 + 3] = x0i + x2i;
+    a[j1 + 2] = x0r - x2r;
+    a[j1 + 3] = x0i - x2i;
+    x0r = x1r - x3i;
+    x0i = x1i + x3r;
+    a[j2 + 2] = wk1i * x0r - wk1r * x0i;
+    a[j2 + 3] = wk1i * x0i + wk1r * x0r;
+    x0r = x1r + x3i;
+    x0i = x1i - x3r;
+    a[j3 + 2] = wk3i * x0r + wk3r * x0i;
+    a[j3 + 3] = wk3i * x0i - wk3r * x0r;
+}
+
+static void cftb1st(int n, double *a, double *w)
+{
+    int j, j0, j1, j2, j3, k, m, mh;
+    double wn4r, csc1, csc3, wk1r, wk1i, wk3r, wk3i,
+        wd1r, wd1i, wd3r, wd3i;
+    double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i,
+        y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i;
+
+    mh = n >> 3;
+    m = 2 * mh;
+    j1 = m;
+    j2 = j1 + m;
+    j3 = j2 + m;
+    x0r = a[0] + a[j2];
+    x0i = -a[1] - a[j2 + 1];
+    x1r = a[0] - a[j2];
+    x1i = -a[1] + a[j2 + 1];
+    x2r = a[j1] + a[j3];
+    x2i = a[j1 + 1] + a[j3 + 1];
+    x3r = a[j1] - a[j3];
+    x3i = a[j1 + 1] - a[j3 + 1];
+    a[0] = x0r + x2r;
+    a[1] = x0i - x2i;
+    a[j1] = x0r - x2r;
+    a[j1 + 1] = x0i + x2i;
+    a[j2] = x1r + x3i;
+    a[j2 + 1] = x1i + x3r;
+    a[j3] = x1r - x3i;
+    a[j3 + 1] = x1i - x3r;
+    wn4r = w[1];
+    csc1 = w[2];
+    csc3 = w[3];
+    wd1r = 1;
+    wd1i = 0;
+    wd3r = 1;
+    wd3i = 0;
+    k = 0;
+    for (j = 2; j < mh - 2; j += 4) {
+        k += 4;
+        wk1r = csc1 * (wd1r + w[k]);
+        wk1i = csc1 * (wd1i + w[k + 1]);
+        wk3r = csc3 * (wd3r + w[k + 2]);
+        wk3i = csc3 * (wd3i + w[k + 3]);
+        wd1r = w[k];
+        wd1i = w[k + 1];
+        wd3r = w[k + 2];
+        wd3i = w[k + 3];
+        j1 = j + m;
+        j2 = j1 + m;
+        j3 = j2 + m;
+        x0r = a[j] + a[j2];
+        x0i = -a[j + 1] - a[j2 + 1];
+        x1r = a[j] - a[j2];
+        x1i = -a[j + 1] + a[j2 + 1];
+        y0r = a[j + 2] + a[j2 + 2];
+        y0i = -a[j + 3] - a[j2 + 3];
+        y1r = a[j + 2] - a[j2 + 2];
+        y1i = -a[j + 3] + a[j2 + 3];
+        x2r = a[j1] + a[j3];
+        x2i = a[j1 + 1] + a[j3 + 1];
+        x3r = a[j1] - a[j3];
+        x3i = a[j1 + 1] - a[j3 + 1];
+        y2r = a[j1 + 2] + a[j3 + 2];
+        y2i = a[j1 + 3] + a[j3 + 3];
+        y3r = a[j1 + 2] - a[j3 + 2];
+        y3i = a[j1 + 3] - a[j3 + 3];
+        a[j] = x0r + x2r;
+        a[j + 1] = x0i - x2i;
+        a[j + 2] = y0r + y2r;
+        a[j + 3] = y0i - y2i;
+        a[j1] = x0r - x2r;
+        a[j1 + 1] = x0i + x2i;
+        a[j1 + 2] = y0r - y2r;
+        a[j1 + 3] = y0i + y2i;
+        x0r = x1r + x3i;
+        x0i = x1i + x3r;
+        a[j2] = wk1r * x0r - wk1i * x0i;
+        a[j2 + 1] = wk1r * x0i + wk1i * x0r;
+        x0r = y1r + y3i;
+        x0i = y1i + y3r;
+        a[j2 + 2] = wd1r * x0r - wd1i * x0i;
+        a[j2 + 3] = wd1r * x0i + wd1i * x0r;
+        x0r = x1r - x3i;
+        x0i = x1i - x3r;
+        a[j3] = wk3r * x0r + wk3i * x0i;
+        a[j3 + 1] = wk3r * x0i - wk3i * x0r;
+        x0r = y1r - y3i;
+        x0i = y1i - y3r;
+        a[j3 + 2] = wd3r * x0r + wd3i * x0i;
+        a[j3 + 3] = wd3r * x0i - wd3i * x0r;
+        j0 = m - j;
+        j1 = j0 + m;
+        j2 = j1 + m;
+        j3 = j2 + m;
+        x0r = a[j0] + a[j2];
+        x0i = -a[j0 + 1] - a[j2 + 1];
+        x1r = a[j0] - a[j2];
+        x1i = -a[j0 + 1] + a[j2 + 1];
+        y0r = a[j0 - 2] + a[j2 - 2];
+        y0i = -a[j0 - 1] - a[j2 - 1];
+        y1r = a[j0 - 2] - a[j2 - 2];
+        y1i = -a[j0 - 1] + a[j2 - 1];
+        x2r = a[j1] + a[j3];
+        x2i = a[j1 + 1] + a[j3 + 1];
+        x3r = a[j1] - a[j3];
+        x3i = a[j1 + 1] - a[j3 + 1];
+        y2r = a[j1 - 2] + a[j3 - 2];
+        y2i = a[j1 - 1] + a[j3 - 1];
+        y3r = a[j1 - 2] - a[j3 - 2];
+        y3i = a[j1 - 1] - a[j3 - 1];
+        a[j0] = x0r + x2r;
+        a[j0 + 1] = x0i - x2i;
+        a[j0 - 2] = y0r + y2r;
+        a[j0 - 1] = y0i - y2i;
+        a[j1] = x0r - x2r;
+        a[j1 + 1] = x0i + x2i;
+        a[j1 - 2] = y0r - y2r;
+        a[j1 - 1] = y0i + y2i;
+        x0r = x1r + x3i;
+        x0i = x1i + x3r;
+        a[j2] = wk1i * x0r - wk1r * x0i;
+        a[j2 + 1] = wk1i * x0i + wk1r * x0r;
+        x0r = y1r + y3i;
+        x0i = y1i + y3r;
+        a[j2 - 2] = wd1i * x0r - wd1r * x0i;
+        a[j2 - 1] = wd1i * x0i + wd1r * x0r;
+        x0r = x1r - x3i;
+        x0i = x1i - x3r;
+        a[j3] = wk3i * x0r + wk3r * x0i;
+        a[j3 + 1] = wk3i * x0i - wk3r * x0r;
+        x0r = y1r - y3i;
+        x0i = y1i - y3r;
+        a[j3 - 2] = wd3i * x0r + wd3r * x0i;
+        a[j3 - 1] = wd3i * x0i - wd3r * x0r;
+    }
+    wk1r = csc1 * (wd1r + wn4r);
+    wk1i = csc1 * (wd1i + wn4r);
+    wk3r = csc3 * (wd3r - wn4r);
+    wk3i = csc3 * (wd3i - wn4r);
+    j0 = mh;
+    j1 = j0 + m;
+    j2 = j1 + m;
+    j3 = j2 + m;
+    x0r = a[j0 - 2] + a[j2 - 2];
+    x0i = -a[j0 - 1] - a[j2 - 1];
+    x1r = a[j0 - 2] - a[j2 - 2];
+    x1i = -a[j0 - 1] + a[j2 - 1];
+    x2r = a[j1 - 2] + a[j3 - 2];
+    x2i = a[j1 - 1] + a[j3 - 1];
+    x3r = a[j1 - 2] - a[j3 - 2];
+    x3i = a[j1 - 1] - a[j3 - 1];
+    a[j0 - 2] = x0r + x2r;
+    a[j0 - 1] = x0i - x2i;
+    a[j1 - 2] = x0r - x2r;
+    a[j1 - 1] = x0i + x2i;
+    x0r = x1r + x3i;
+    x0i = x1i + x3r;
+    a[j2 - 2] = wk1r * x0r - wk1i * x0i;
+    a[j2 - 1] = wk1r * x0i + wk1i * x0r;
+    x0r = x1r - x3i;
+    x0i = x1i - x3r;
+    a[j3 - 2] = wk3r * x0r + wk3i * x0i;
+    a[j3 - 1] = wk3r * x0i - wk3i * x0r;
+    x0r = a[j0] + a[j2];
+    x0i = -a[j0 + 1] - a[j2 + 1];
+    x1r = a[j0] - a[j2];
+    x1i = -a[j0 + 1] + a[j2 + 1];
+    x2r = a[j1] + a[j3];
+    x2i = a[j1 + 1] + a[j3 + 1];
+    x3r = a[j1] - a[j3];
+    x3i = a[j1 + 1] - a[j3 + 1];
+    a[j0] = x0r + x2r;
+    a[j0 + 1] = x0i - x2i;
+    a[j1] = x0r - x2r;
+    a[j1 + 1] = x0i + x2i;
+    x0r = x1r + x3i;
+    x0i = x1i + x3r;
+    a[j2] = wn4r * (x0r - x0i);
+    a[j2 + 1] = wn4r * (x0i + x0r);
+    x0r = x1r - x3i;
+    x0i = x1i - x3r;
+    a[j3] = -wn4r * (x0r + x0i);
+    a[j3 + 1] = -wn4r * (x0i - x0r);
+    x0r = a[j0 + 2] + a[j2 + 2];
+    x0i = -a[j0 + 3] - a[j2 + 3];
+    x1r = a[j0 + 2] - a[j2 + 2];
+    x1i = -a[j0 + 3] + a[j2 + 3];
+    x2r = a[j1 + 2] + a[j3 + 2];
+    x2i = a[j1 + 3] + a[j3 + 3];
+    x3r = a[j1 + 2] - a[j3 + 2];
+    x3i = a[j1 + 3] - a[j3 + 3];
+    a[j0 + 2] = x0r + x2r;
+    a[j0 + 3] = x0i - x2i;
+    a[j1 + 2] = x0r - x2r;
+    a[j1 + 3] = x0i + x2i;
+    x0r = x1r + x3i;
+    x0i = x1i + x3r;
+    a[j2 + 2] = wk1i * x0r - wk1r * x0i;
+    a[j2 + 3] = wk1i * x0i + wk1r * x0r;
+    x0r = x1r - x3i;
+    x0i = x1i - x3r;
+    a[j3 + 2] = wk3i * x0r + wk3r * x0i;
+    a[j3 + 3] = wk3i * x0i - wk3r * x0r;
+}
+
+static void cftmdl1(int n, double *a, double *w)
+{
+    int j, j0, j1, j2, j3, k, m, mh;
+    double wn4r, wk1r, wk1i, wk3r, wk3i;
+    double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+    mh = n >> 3;
+    m = 2 * mh;
+    j1 = m;
+    j2 = j1 + m;
+    j3 = j2 + m;
+    x0r = a[0] + a[j2];
+    x0i = a[1] + a[j2 + 1];
+    x1r = a[0] - a[j2];
+    x1i = a[1] - a[j2 + 1];
+    x2r = a[j1] + a[j3];
+    x2i = a[j1 + 1] + a[j3 + 1];
+    x3r = a[j1] - a[j3];
+    x3i = a[j1 + 1] - a[j3 + 1];
+    a[0] = x0r + x2r;
+    a[1] = x0i + x2i;
+    a[j1] = x0r - x2r;
+    a[j1 + 1] = x0i - x2i;
+    a[j2] = x1r - x3i;
+    a[j2 + 1] = x1i + x3r;
+    a[j3] = x1r + x3i;
+    a[j3 + 1] = x1i - x3r;
+    wn4r = w[1];
+    k = 0;
+    for (j = 2; j < mh; j += 2) {
+        k += 4;
+        wk1r = w[k];
+        wk1i = w[k + 1];
+        wk3r = w[k + 2];
+        wk3i = w[k + 3];
+        j1 = j + m;
+        j2 = j1 + m;
+        j3 = j2 + m;
+        x0r = a[j] + a[j2];
+        x0i = a[j + 1] + a[j2 + 1];
+        x1r = a[j] - a[j2];
+        x1i = a[j + 1] - a[j2 + 1];
+        x2r = a[j1] + a[j3];
+        x2i = a[j1 + 1] + a[j3 + 1];
+        x3r = a[j1] - a[j3];
+        x3i = a[j1 + 1] - a[j3 + 1];
+        a[j] = x0r + x2r;
+        a[j + 1] = x0i + x2i;
+        a[j1] = x0r - x2r;
+        a[j1 + 1] = x0i - x2i;
+        x0r = x1r - x3i;
+        x0i = x1i + x3r;
+        a[j2] = wk1r * x0r - wk1i * x0i;
+        a[j2 + 1] = wk1r * x0i + wk1i * x0r;
+        x0r = x1r + x3i;
+        x0i = x1i - x3r;
+        a[j3] = wk3r * x0r + wk3i * x0i;
+        a[j3 + 1] = wk3r * x0i - wk3i * x0r;
+        j0 = m - j;
+        j1 = j0 + m;
+        j2 = j1 + m;
+        j3 = j2 + m;
+        x0r = a[j0] + a[j2];
+        x0i = a[j0 + 1] + a[j2 + 1];
+        x1r = a[j0] - a[j2];
+        x1i = a[j0 + 1] - a[j2 + 1];
+        x2r = a[j1] + a[j3];
+        x2i = a[j1 + 1] + a[j3 + 1];
+        x3r = a[j1] - a[j3];
+        x3i = a[j1 + 1] - a[j3 + 1];
+        a[j0] = x0r + x2r;
+        a[j0 + 1] = x0i + x2i;
+        a[j1] = x0r - x2r;
+        a[j1 + 1] = x0i - x2i;
+        x0r = x1r - x3i;
+        x0i = x1i + x3r;
+        a[j2] = wk1i * x0r - wk1r * x0i;
+        a[j2 + 1] = wk1i * x0i + wk1r * x0r;
+        x0r = x1r + x3i;
+        x0i = x1i - x3r;
+        a[j3] = wk3i * x0r + wk3r * x0i;
+        a[j3 + 1] = wk3i * x0i - wk3r * x0r;
+    }
+    j0 = mh;
+    j1 = j0 + m;
+    j2 = j1 + m;
+    j3 = j2 + m;
+    x0r = a[j0] + a[j2];
+    x0i = a[j0 + 1] + a[j2 + 1];
+    x1r = a[j0] - a[j2];
+    x1i = a[j0 + 1] - a[j2 + 1];
+    x2r = a[j1] + a[j3];
+    x2i = a[j1 + 1] + a[j3 + 1];
+    x3r = a[j1] - a[j3];
+    x3i = a[j1 + 1] - a[j3 + 1];
+    a[j0] = x0r + x2r;
+    a[j0 + 1] = x0i + x2i;
+    a[j1] = x0r - x2r;
+    a[j1 + 1] = x0i - x2i;
+    x0r = x1r - x3i;
+    x0i = x1i + x3r;
+    a[j2] = wn4r * (x0r - x0i);
+    a[j2 + 1] = wn4r * (x0i + x0r);
+    x0r = x1r + x3i;
+    x0i = x1i - x3r;
+    a[j3] = -wn4r * (x0r + x0i);
+    a[j3 + 1] = -wn4r * (x0i - x0r);
+}
+
+static void cftmdl2(int n, double *a, double *w)
+{
+    int j, j0, j1, j2, j3, k, kr, m, mh;
+    double wn4r, wk1r, wk1i, wk3r, wk3i, wd1r, wd1i, wd3r, wd3i;
+    double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, y0r, y0i, y2r, y2i;
+
+    mh = n >> 3;
+    m = 2 * mh;
+    wn4r = w[1];
+    j1 = m;
+    j2 = j1 + m;
+    j3 = j2 + m;
+    x0r = a[0] - a[j2 + 1];
+    x0i = a[1] + a[j2];
+    x1r = a[0] + a[j2 + 1];
+    x1i = a[1] - a[j2];
+    x2r = a[j1] - a[j3 + 1];
+    x2i = a[j1 + 1] + a[j3];
+    x3r = a[j1] + a[j3 + 1];
+    x3i = a[j1 + 1] - a[j3];
+    y0r = wn4r * (x2r - x2i);
+    y0i = wn4r * (x2i + x2r);
+    a[0] = x0r + y0r;
+    a[1] = x0i + y0i;
+    a[j1] = x0r - y0r;
+    a[j1 + 1] = x0i - y0i;
+    y0r = wn4r * (x3r - x3i);
+    y0i = wn4r * (x3i + x3r);
+    a[j2] = x1r - y0i;
+    a[j2 + 1] = x1i + y0r;
+    a[j3] = x1r + y0i;
+    a[j3 + 1] = x1i - y0r;
+    k = 0;
+    kr = 2 * m;
+    for (j = 2; j < mh; j += 2) {
+        k += 4;
+        wk1r = w[k];
+        wk1i = w[k + 1];
+        wk3r = w[k + 2];
+        wk3i = w[k + 3];
+        kr -= 4;
+        wd1i = w[kr];
+        wd1r = w[kr + 1];
+        wd3i = w[kr + 2];
+        wd3r = w[kr + 3];
+        j1 = j + m;
+        j2 = j1 + m;
+        j3 = j2 + m;
+        x0r = a[j] - a[j2 + 1];
+        x0i = a[j + 1] + a[j2];
+        x1r = a[j] + a[j2 + 1];
+        x1i = a[j + 1] - a[j2];
+        x2r = a[j1] - a[j3 + 1];
+        x2i = a[j1 + 1] + a[j3];
+        x3r = a[j1] + a[j3 + 1];
+        x3i = a[j1 + 1] - a[j3];
+        y0r = wk1r * x0r - wk1i * x0i;
+        y0i = wk1r * x0i + wk1i * x0r;
+        y2r = wd1r * x2r - wd1i * x2i;
+        y2i = wd1r * x2i + wd1i * x2r;
+        a[j] = y0r + y2r;
+        a[j + 1] = y0i + y2i;
+        a[j1] = y0r - y2r;
+        a[j1 + 1] = y0i - y2i;
+        y0r = wk3r * x1r + wk3i * x1i;
+        y0i = wk3r * x1i - wk3i * x1r;
+        y2r = wd3r * x3r + wd3i * x3i;
+        y2i = wd3r * x3i - wd3i * x3r;
+        a[j2] = y0r + y2r;
+        a[j2 + 1] = y0i + y2i;
+        a[j3] = y0r - y2r;
+        a[j3 + 1] = y0i - y2i;
+        j0 = m - j;
+        j1 = j0 + m;
+        j2 = j1 + m;
+        j3 = j2 + m;
+        x0r = a[j0] - a[j2 + 1];
+        x0i = a[j0 + 1] + a[j2];
+        x1r = a[j0] + a[j2 + 1];
+        x1i = a[j0 + 1] - a[j2];
+        x2r = a[j1] - a[j3 + 1];
+        x2i = a[j1 + 1] + a[j3];
+        x3r = a[j1] + a[j3 + 1];
+        x3i = a[j1 + 1] - a[j3];
+        y0r = wd1i * x0r - wd1r * x0i;
+        y0i = wd1i * x0i + wd1r * x0r;
+        y2r = wk1i * x2r - wk1r * x2i;
+        y2i = wk1i * x2i + wk1r * x2r;
+        a[j0] = y0r + y2r;
+        a[j0 + 1] = y0i + y2i;
+        a[j1] = y0r - y2r;
+        a[j1 + 1] = y0i - y2i;
+        y0r = wd3i * x1r + wd3r * x1i;
+        y0i = wd3i * x1i - wd3r * x1r;
+        y2r = wk3i * x3r + wk3r * x3i;
+        y2i = wk3i * x3i - wk3r * x3r;
+        a[j2] = y0r + y2r;
+        a[j2 + 1] = y0i + y2i;
+        a[j3] = y0r - y2r;
+        a[j3 + 1] = y0i - y2i;
+    }
+    wk1r = w[m];
+    wk1i = w[m + 1];
+    j0 = mh;
+    j1 = j0 + m;
+    j2 = j1 + m;
+    j3 = j2 + m;
+    x0r = a[j0] - a[j2 + 1];
+    x0i = a[j0 + 1] + a[j2];
+    x1r = a[j0] + a[j2 + 1];
+    x1i = a[j0 + 1] - a[j2];
+    x2r = a[j1] - a[j3 + 1];
+    x2i = a[j1 + 1] + a[j3];
+    x3r = a[j1] + a[j3 + 1];
+    x3i = a[j1 + 1] - a[j3];
+    y0r = wk1r * x0r - wk1i * x0i;
+    y0i = wk1r * x0i + wk1i * x0r;
+    y2r = wk1i * x2r - wk1r * x2i;
+    y2i = wk1i * x2i + wk1r * x2r;
+    a[j0] = y0r + y2r;
+    a[j0 + 1] = y0i + y2i;
+    a[j1] = y0r - y2r;
+    a[j1 + 1] = y0i - y2i;
+    y0r = wk1i * x1r - wk1r * x1i;
+    y0i = wk1i * x1i + wk1r * x1r;
+    y2r = wk1r * x3r - wk1i * x3i;
+    y2i = wk1r * x3i + wk1i * x3r;
+    a[j2] = y0r - y2r;
+    a[j2 + 1] = y0i - y2i;
+    a[j3] = y0r + y2r;
+    a[j3 + 1] = y0i + y2i;
+}
+
+static int cfttree(int n, int j, int k, double *a, int nw, double *w)
+{
+    int i, isplt, m;
+
+    if ((k & 3) != 0) {
+        isplt = k & 1;
+        if (isplt != 0) {
+            cftmdl1(n, &a[j - n], &w[nw - (n >> 1)]);
+        } else {
+            cftmdl2(n, &a[j - n], &w[nw - n]);
+        }
+    } else {
+        m = n;
+        for (i = k; (i & 3) == 0; i >>= 2) {
+            m <<= 2;
+        }
+        isplt = i & 1;
+        if (isplt != 0) {
+            while (m > 128) {
+                cftmdl1(m, &a[j - m], &w[nw - (m >> 1)]);
+                m >>= 2;
+            }
+        } else {
+            while (m > 128) {
+                cftmdl2(m, &a[j - m], &w[nw - m]);
+                m >>= 2;
+            }
+        }
+    }
+    return isplt;
+}
+
+static void cftf161(double *a, double *w)
+{
+    double wn4r, wk1r, wk1i,
+        x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i,
+        y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i,
+        y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i,
+        y8r, y8i, y9r, y9i, y10r, y10i, y11r, y11i,
+        y12r, y12i, y13r, y13i, y14r, y14i, y15r, y15i;
+
+    wn4r = w[1];
+    wk1r = w[2];
+    wk1i = w[3];
+    x0r = a[0] + a[16];
+    x0i = a[1] + a[17];
+    x1r = a[0] - a[16];
+    x1i = a[1] - a[17];
+    x2r = a[8] + a[24];
+    x2i = a[9] + a[25];
+    x3r = a[8] - a[24];
+    x3i = a[9] - a[25];
+    y0r = x0r + x2r;
+    y0i = x0i + x2i;
+    y4r = x0r - x2r;
+    y4i = x0i - x2i;
+    y8r = x1r - x3i;
+    y8i = x1i + x3r;
+    y12r = x1r + x3i;
+    y12i = x1i - x3r;
+    x0r = a[2] + a[18];
+    x0i = a[3] + a[19];
+    x1r = a[2] - a[18];
+    x1i = a[3] - a[19];
+    x2r = a[10] + a[26];
+    x2i = a[11] + a[27];
+    x3r = a[10] - a[26];
+    x3i = a[11] - a[27];
+    y1r = x0r + x2r;
+    y1i = x0i + x2i;
+    y5r = x0r - x2r;
+    y5i = x0i - x2i;
+    x0r = x1r - x3i;
+    x0i = x1i + x3r;
+    y9r = wk1r * x0r - wk1i * x0i;
+    y9i = wk1r * x0i + wk1i * x0r;
+    x0r = x1r + x3i;
+    x0i = x1i - x3r;
+    y13r = wk1i * x0r - wk1r * x0i;
+    y13i = wk1i * x0i + wk1r * x0r;
+    x0r = a[4] + a[20];
+    x0i = a[5] + a[21];
+    x1r = a[4] - a[20];
+    x1i = a[5] - a[21];
+    x2r = a[12] + a[28];
+    x2i = a[13] + a[29];
+    x3r = a[12] - a[28];
+    x3i = a[13] - a[29];
+    y2r = x0r + x2r;
+    y2i = x0i + x2i;
+    y6r = x0r - x2r;
+    y6i = x0i - x2i;
+    x0r = x1r - x3i;
+    x0i = x1i + x3r;
+    y10r = wn4r * (x0r - x0i);
+    y10i = wn4r * (x0i + x0r);
+    x0r = x1r + x3i;
+    x0i = x1i - x3r;
+    y14r = wn4r * (x0r + x0i);
+    y14i = wn4r * (x0i - x0r);
+    x0r = a[6] + a[22];
+    x0i = a[7] + a[23];
+    x1r = a[6] - a[22];
+    x1i = a[7] - a[23];
+    x2r = a[14] + a[30];
+    x2i = a[15] + a[31];
+    x3r = a[14] - a[30];
+    x3i = a[15] - a[31];
+    y3r = x0r + x2r;
+    y3i = x0i + x2i;
+    y7r = x0r - x2r;
+    y7i = x0i - x2i;
+    x0r = x1r - x3i;
+    x0i = x1i + x3r;
+    y11r = wk1i * x0r - wk1r * x0i;
+    y11i = wk1i * x0i + wk1r * x0r;
+    x0r = x1r + x3i;
+    x0i = x1i - x3r;
+    y15r = wk1r * x0r - wk1i * x0i;
+    y15i = wk1r * x0i + wk1i * x0r;
+    x0r = y12r - y14r;
+    x0i = y12i - y14i;
+    x1r = y12r + y14r;
+    x1i = y12i + y14i;
+    x2r = y13r - y15r;
+    x2i = y13i - y15i;
+    x3r = y13r + y15r;
+    x3i = y13i + y15i;
+    a[24] = x0r + x2r;
+    a[25] = x0i + x2i;
+    a[26] = x0r - x2r;
+    a[27] = x0i - x2i;
+    a[28] = x1r - x3i;
+    a[29] = x1i + x3r;
+    a[30] = x1r + x3i;
+    a[31] = x1i - x3r;
+    x0r = y8r + y10r;
+    x0i = y8i + y10i;
+    x1r = y8r - y10r;
+    x1i = y8i - y10i;
+    x2r = y9r + y11r;
+    x2i = y9i + y11i;
+    x3r = y9r - y11r;
+    x3i = y9i - y11i;
+    a[16] = x0r + x2r;
+    a[17] = x0i + x2i;
+    a[18] = x0r - x2r;
+    a[19] = x0i - x2i;
+    a[20] = x1r - x3i;
+    a[21] = x1i + x3r;
+    a[22] = x1r + x3i;
+    a[23] = x1i - x3r;
+    x0r = y5r - y7i;
+    x0i = y5i + y7r;
+    x2r = wn4r * (x0r - x0i);
+    x2i = wn4r * (x0i + x0r);
+    x0r = y5r + y7i;
+    x0i = y5i - y7r;
+    x3r = wn4r * (x0r - x0i);
+    x3i = wn4r * (x0i + x0r);
+    x0r = y4r - y6i;
+    x0i = y4i + y6r;
+    x1r = y4r + y6i;
+    x1i = y4i - y6r;
+    a[8] = x0r + x2r;
+    a[9] = x0i + x2i;
+    a[10] = x0r - x2r;
+    a[11] = x0i - x2i;
+    a[12] = x1r - x3i;
+    a[13] = x1i + x3r;
+    a[14] = x1r + x3i;
+    a[15] = x1i - x3r;
+    x0r = y0r + y2r;
+    x0i = y0i + y2i;
+    x1r = y0r - y2r;
+    x1i = y0i - y2i;
+    x2r = y1r + y3r;
+    x2i = y1i + y3i;
+    x3r = y1r - y3r;
+    x3i = y1i - y3i;
+    a[0] = x0r + x2r;
+    a[1] = x0i + x2i;
+    a[2] = x0r - x2r;
+    a[3] = x0i - x2i;
+    a[4] = x1r - x3i;
+    a[5] = x1i + x3r;
+    a[6] = x1r + x3i;
+    a[7] = x1i - x3r;
+}
+
+static void cftf162(double *a, double *w)
+{
+    double wn4r, wk1r, wk1i, wk2r, wk2i, wk3r, wk3i,
+        x0r, x0i, x1r, x1i, x2r, x2i,
+        y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i,
+        y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i,
+        y8r, y8i, y9r, y9i, y10r, y10i, y11r, y11i,
+        y12r, y12i, y13r, y13i, y14r, y14i, y15r, y15i;
+
+    wn4r = w[1];
+    wk1r = w[4];
+    wk1i = w[5];
+    wk3r = w[6];
+    wk3i = -w[7];
+    wk2r = w[8];
+    wk2i = w[9];
+    x1r = a[0] - a[17];
+    x1i = a[1] + a[16];
+    x0r = a[8] - a[25];
+    x0i = a[9] + a[24];
+    x2r = wn4r * (x0r - x0i);
+    x2i = wn4r * (x0i + x0r);
+    y0r = x1r + x2r;
+    y0i = x1i + x2i;
+    y4r = x1r - x2r;
+    y4i = x1i - x2i;
+    x1r = a[0] + a[17];
+    x1i = a[1] - a[16];
+    x0r = a[8] + a[25];
+    x0i = a[9] - a[24];
+    x2r = wn4r * (x0r - x0i);
+    x2i = wn4r * (x0i + x0r);
+    y8r = x1r - x2i;
+    y8i = x1i + x2r;
+    y12r = x1r + x2i;
+    y12i = x1i - x2r;
+    x0r = a[2] - a[19];
+    x0i = a[3] + a[18];
+    x1r = wk1r * x0r - wk1i * x0i;
+    x1i = wk1r * x0i + wk1i * x0r;
+    x0r = a[10] - a[27];
+    x0i = a[11] + a[26];
+    x2r = wk3i * x0r - wk3r * x0i;
+    x2i = wk3i * x0i + wk3r * x0r;
+    y1r = x1r + x2r;
+    y1i = x1i + x2i;
+    y5r = x1r - x2r;
+    y5i = x1i - x2i;
+    x0r = a[2] + a[19];
+    x0i = a[3] - a[18];
+    x1r = wk3r * x0r - wk3i * x0i;
+    x1i = wk3r * x0i + wk3i * x0r;
+    x0r = a[10] + a[27];
+    x0i = a[11] - a[26];
+    x2r = wk1r * x0r + wk1i * x0i;
+    x2i = wk1r * x0i - wk1i * x0r;
+    y9r = x1r - x2r;
+    y9i = x1i - x2i;
+    y13r = x1r + x2r;
+    y13i = x1i + x2i;
+    x0r = a[4] - a[21];
+    x0i = a[5] + a[20];
+    x1r = wk2r * x0r - wk2i * x0i;
+    x1i = wk2r * x0i + wk2i * x0r;
+    x0r = a[12] - a[29];
+    x0i = a[13] + a[28];
+    x2r = wk2i * x0r - wk2r * x0i;
+    x2i = wk2i * x0i + wk2r * x0r;
+    y2r = x1r + x2r;
+    y2i = x1i + x2i;
+    y6r = x1r - x2r;
+    y6i = x1i - x2i;
+    x0r = a[4] + a[21];
+    x0i = a[5] - a[20];
+    x1r = wk2i * x0r - wk2r * x0i;
+    x1i = wk2i * x0i + wk2r * x0r;
+    x0r = a[12] + a[29];
+    x0i = a[13] - a[28];
+    x2r = wk2r * x0r - wk2i * x0i;
+    x2i = wk2r * x0i + wk2i * x0r;
+    y10r = x1r - x2r;
+    y10i = x1i - x2i;
+    y14r = x1r + x2r;
+    y14i = x1i + x2i;
+    x0r = a[6] - a[23];
+    x0i = a[7] + a[22];
+    x1r = wk3r * x0r - wk3i * x0i;
+    x1i = wk3r * x0i + wk3i * x0r;
+    x0r = a[14] - a[31];
+    x0i = a[15] + a[30];
+    x2r = wk1i * x0r - wk1r * x0i;
+    x2i = wk1i * x0i + wk1r * x0r;
+    y3r = x1r + x2r;
+    y3i = x1i + x2i;
+    y7r = x1r - x2r;
+    y7i = x1i - x2i;
+    x0r = a[6] + a[23];
+    x0i = a[7] - a[22];
+    x1r = wk1i * x0r + wk1r * x0i;
+    x1i = wk1i * x0i - wk1r * x0r;
+    x0r = a[14] + a[31];
+    x0i = a[15] - a[30];
+    x2r = wk3i * x0r - wk3r * x0i;
+    x2i = wk3i * x0i + wk3r * x0r;
+    y11r = x1r + x2r;
+    y11i = x1i + x2i;
+    y15r = x1r - x2r;
+    y15i = x1i - x2i;
+    x1r = y0r + y2r;
+    x1i = y0i + y2i;
+    x2r = y1r + y3r;
+    x2i = y1i + y3i;
+    a[0] = x1r + x2r;
+    a[1] = x1i + x2i;
+    a[2] = x1r - x2r;
+    a[3] = x1i - x2i;
+    x1r = y0r - y2r;
+    x1i = y0i - y2i;
+    x2r = y1r - y3r;
+    x2i = y1i - y3i;
+    a[4] = x1r - x2i;
+    a[5] = x1i + x2r;
+    a[6] = x1r + x2i;
+    a[7] = x1i - x2r;
+    x1r = y4r - y6i;
+    x1i = y4i + y6r;
+    x0r = y5r - y7i;
+    x0i = y5i + y7r;
+    x2r = wn4r * (x0r - x0i);
+    x2i = wn4r * (x0i + x0r);
+    a[8] = x1r + x2r;
+    a[9] = x1i + x2i;
+    a[10] = x1r - x2r;
+    a[11] = x1i - x2i;
+    x1r = y4r + y6i;
+    x1i = y4i - y6r;
+    x0r = y5r + y7i;
+    x0i = y5i - y7r;
+    x2r = wn4r * (x0r - x0i);
+    x2i = wn4r * (x0i + x0r);
+    a[12] = x1r - x2i;
+    a[13] = x1i + x2r;
+    a[14] = x1r + x2i;
+    a[15] = x1i - x2r;
+    x1r = y8r + y10r;
+    x1i = y8i + y10i;
+    x2r = y9r - y11r;
+    x2i = y9i - y11i;
+    a[16] = x1r + x2r;
+    a[17] = x1i + x2i;
+    a[18] = x1r - x2r;
+    a[19] = x1i - x2i;
+    x1r = y8r - y10r;
+    x1i = y8i - y10i;
+    x2r = y9r + y11r;
+    x2i = y9i + y11i;
+    a[20] = x1r - x2i;
+    a[21] = x1i + x2r;
+    a[22] = x1r + x2i;
+    a[23] = x1i - x2r;
+    x1r = y12r - y14i;
+    x1i = y12i + y14r;
+    x0r = y13r + y15i;
+    x0i = y13i - y15r;
+    x2r = wn4r * (x0r - x0i);
+    x2i = wn4r * (x0i + x0r);
+    a[24] = x1r + x2r;
+    a[25] = x1i + x2i;
+    a[26] = x1r - x2r;
+    a[27] = x1i - x2i;
+    x1r = y12r + y14i;
+    x1i = y12i - y14r;
+    x0r = y13r - y15i;
+    x0i = y13i + y15r;
+    x2r = wn4r * (x0r - x0i);
+    x2i = wn4r * (x0i + x0r);
+    a[28] = x1r - x2i;
+    a[29] = x1i + x2r;
+    a[30] = x1r + x2i;
+    a[31] = x1i - x2r;
+}
+
+static void cftf081(double *a, double *w)
+{
+    double wn4r, x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i,
+        y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i,
+        y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i;
+
+    wn4r = w[1];
+    x0r = a[0] + a[8];
+    x0i = a[1] + a[9];
+    x1r = a[0] - a[8];
+    x1i = a[1] - a[9];
+    x2r = a[4] + a[12];
+    x2i = a[5] + a[13];
+    x3r = a[4] - a[12];
+    x3i = a[5] - a[13];
+    y0r = x0r + x2r;
+    y0i = x0i + x2i;
+    y2r = x0r - x2r;
+    y2i = x0i - x2i;
+    y1r = x1r - x3i;
+    y1i = x1i + x3r;
+    y3r = x1r + x3i;
+    y3i = x1i - x3r;
+    x0r = a[2] + a[10];
+    x0i = a[3] + a[11];
+    x1r = a[2] - a[10];
+    x1i = a[3] - a[11];
+    x2r = a[6] + a[14];
+    x2i = a[7] + a[15];
+    x3r = a[6] - a[14];
+    x3i = a[7] - a[15];
+    y4r = x0r + x2r;
+    y4i = x0i + x2i;
+    y6r = x0r - x2r;
+    y6i = x0i - x2i;
+    x0r = x1r - x3i;
+    x0i = x1i + x3r;
+    x2r = x1r + x3i;
+    x2i = x1i - x3r;
+    y5r = wn4r * (x0r - x0i);
+    y5i = wn4r * (x0r + x0i);
+    y7r = wn4r * (x2r - x2i);
+    y7i = wn4r * (x2r + x2i);
+    a[8] = y1r + y5r;
+    a[9] = y1i + y5i;
+    a[10] = y1r - y5r;
+    a[11] = y1i - y5i;
+    a[12] = y3r - y7i;
+    a[13] = y3i + y7r;
+    a[14] = y3r + y7i;
+    a[15] = y3i - y7r;
+    a[0] = y0r + y4r;
+    a[1] = y0i + y4i;
+    a[2] = y0r - y4r;
+    a[3] = y0i - y4i;
+    a[4] = y2r - y6i;
+    a[5] = y2i + y6r;
+    a[6] = y2r + y6i;
+    a[7] = y2i - y6r;
+}
+
+static void cftf082(double *a, double *w)
+{
+    double wn4r, wk1r, wk1i, x0r, x0i, x1r, x1i,
+        y0r, y0i, y1r, y1i, y2r, y2i, y3r, y3i,
+        y4r, y4i, y5r, y5i, y6r, y6i, y7r, y7i;
+
+    wn4r = w[1];
+    wk1r = w[2];
+    wk1i = w[3];
+    y0r = a[0] - a[9];
+    y0i = a[1] + a[8];
+    y1r = a[0] + a[9];
+    y1i = a[1] - a[8];
+    x0r = a[4] - a[13];
+    x0i = a[5] + a[12];
+    y2r = wn4r * (x0r - x0i);
+    y2i = wn4r * (x0i + x0r);
+    x0r = a[4] + a[13];
+    x0i = a[5] - a[12];
+    y3r = wn4r * (x0r - x0i);
+    y3i = wn4r * (x0i + x0r);
+    x0r = a[2] - a[11];
+    x0i = a[3] + a[10];
+    y4r = wk1r * x0r - wk1i * x0i;
+    y4i = wk1r * x0i + wk1i * x0r;
+    x0r = a[2] + a[11];
+    x0i = a[3] - a[10];
+    y5r = wk1i * x0r - wk1r * x0i;
+    y5i = wk1i * x0i + wk1r * x0r;
+    x0r = a[6] - a[15];
+    x0i = a[7] + a[14];
+    y6r = wk1i * x0r - wk1r * x0i;
+    y6i = wk1i * x0i + wk1r * x0r;
+    x0r = a[6] + a[15];
+    x0i = a[7] - a[14];
+    y7r = wk1r * x0r - wk1i * x0i;
+    y7i = wk1r * x0i + wk1i * x0r;
+    x0r = y0r + y2r;
+    x0i = y0i + y2i;
+    x1r = y4r + y6r;
+    x1i = y4i + y6i;
+    a[0] = x0r + x1r;
+    a[1] = x0i + x1i;
+    a[2] = x0r - x1r;
+    a[3] = x0i - x1i;
+    x0r = y0r - y2r;
+    x0i = y0i - y2i;
+    x1r = y4r - y6r;
+    x1i = y4i - y6i;
+    a[4] = x0r - x1i;
+    a[5] = x0i + x1r;
+    a[6] = x0r + x1i;
+    a[7] = x0i - x1r;
+    x0r = y1r - y3i;
+    x0i = y1i + y3r;
+    x1r = y5r - y7r;
+    x1i = y5i - y7i;
+    a[8] = x0r + x1r;
+    a[9] = x0i + x1i;
+    a[10] = x0r - x1r;
+    a[11] = x0i - x1i;
+    x0r = y1r + y3i;
+    x0i = y1i - y3r;
+    x1r = y5r + y7r;
+    x1i = y5i + y7i;
+    a[12] = x0r - x1i;
+    a[13] = x0i + x1r;
+    a[14] = x0r + x1i;
+    a[15] = x0i - x1r;
+}
+
+static void cftf040(double *a)
+{
+    double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+    x0r = a[0] + a[4];
+    x0i = a[1] + a[5];
+    x1r = a[0] - a[4];
+    x1i = a[1] - a[5];
+    x2r = a[2] + a[6];
+    x2i = a[3] + a[7];
+    x3r = a[2] - a[6];
+    x3i = a[3] - a[7];
+    a[0] = x0r + x2r;
+    a[1] = x0i + x2i;
+    a[2] = x1r - x3i;
+    a[3] = x1i + x3r;
+    a[4] = x0r - x2r;
+    a[5] = x0i - x2i;
+    a[6] = x1r + x3i;
+    a[7] = x1i - x3r;
+}
+
+static void cftb040(double *a)
+{
+    double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
+
+    x0r = a[0] + a[4];
+    x0i = a[1] + a[5];
+    x1r = a[0] - a[4];
+    x1i = a[1] - a[5];
+    x2r = a[2] + a[6];
+    x2i = a[3] + a[7];
+    x3r = a[2] - a[6];
+    x3i = a[3] - a[7];
+    a[0] = x0r + x2r;
+    a[1] = x0i + x2i;
+    a[2] = x1r + x3i;
+    a[3] = x1i - x3r;
+    a[4] = x0r - x2r;
+    a[5] = x0i - x2i;
+    a[6] = x1r - x3i;
+    a[7] = x1i + x3r;
+}
+
+static void cftx020(double *a)
+{
+    double x0r, x0i;
+
+    x0r = a[0] - a[2];
+    x0i = a[1] - a[3];
+    a[0] += a[2];
+    a[1] += a[3];
+    a[2] = x0r;
+    a[3] = x0i;
+}
+
+static void cftfx41(int n, double *a, int nw, double *w)
+{
+    if (n == 128) {
+        cftf161(a, &w[nw - 8]);
+        cftf162(&a[32], &w[nw - 32]);
+        cftf161(&a[64], &w[nw - 8]);
+        cftf161(&a[96], &w[nw - 8]);
+    } else {
+        cftf081(a, &w[nw - 8]);
+        cftf082(&a[16], &w[nw - 8]);
+        cftf081(&a[32], &w[nw - 8]);
+        cftf081(&a[48], &w[nw - 8]);
+    }
+}
+
+static void cftleaf(int n, int isplt, double *a, int nw, double *w)
+{
+    if (n == 512) {
+        cftmdl1(128, a, &w[nw - 64]);
+        cftf161(a, &w[nw - 8]);
+        cftf162(&a[32], &w[nw - 32]);
+        cftf161(&a[64], &w[nw - 8]);
+        cftf161(&a[96], &w[nw - 8]);
+        cftmdl2(128, &a[128], &w[nw - 128]);
+        cftf161(&a[128], &w[nw - 8]);
+        cftf162(&a[160], &w[nw - 32]);
+        cftf161(&a[192], &w[nw - 8]);
+        cftf162(&a[224], &w[nw - 32]);
+        cftmdl1(128, &a[256], &w[nw - 64]);
+        cftf161(&a[256], &w[nw - 8]);
+        cftf162(&a[288], &w[nw - 32]);
+        cftf161(&a[320], &w[nw - 8]);
+        cftf161(&a[352], &w[nw - 8]);
+        if (isplt != 0) {
+            cftmdl1(128, &a[384], &w[nw - 64]);
+            cftf161(&a[480], &w[nw - 8]);
+        } else {
+            cftmdl2(128, &a[384], &w[nw - 128]);
+            cftf162(&a[480], &w[nw - 32]);
+        }
+        cftf161(&a[384], &w[nw - 8]);
+        cftf162(&a[416], &w[nw - 32]);
+        cftf161(&a[448], &w[nw - 8]);
+    } else {
+        cftmdl1(64, a, &w[nw - 32]);
+        cftf081(a, &w[nw - 8]);
+        cftf082(&a[16], &w[nw - 8]);
+        cftf081(&a[32], &w[nw - 8]);
+        cftf081(&a[48], &w[nw - 8]);
+        cftmdl2(64, &a[64], &w[nw - 64]);
+        cftf081(&a[64], &w[nw - 8]);
+        cftf082(&a[80], &w[nw - 8]);
+        cftf081(&a[96], &w[nw - 8]);
+        cftf082(&a[112], &w[nw - 8]);
+        cftmdl1(64, &a[128], &w[nw - 32]);
+        cftf081(&a[128], &w[nw - 8]);
+        cftf082(&a[144], &w[nw - 8]);
+        cftf081(&a[160], &w[nw - 8]);
+        cftf081(&a[176], &w[nw - 8]);
+        if (isplt != 0) {
+            cftmdl1(64, &a[192], &w[nw - 32]);
+            cftf081(&a[240], &w[nw - 8]);
+        } else {
+            cftmdl2(64, &a[192], &w[nw - 64]);
+            cftf082(&a[240], &w[nw - 8]);
+        }
+        cftf081(&a[192], &w[nw - 8]);
+        cftf082(&a[208], &w[nw - 8]);
+        cftf081(&a[224], &w[nw - 8]);
+    }
+}
+
+static void cftrec4(int n, double *a, int nw, double *w)
+{
+    int isplt, j, k, m;
+
+    m = n;
+    while (m > 512) {
+        m >>= 2;
+        cftmdl1(m, &a[n - m], &w[nw - (m >> 1)]);
+    }
+    cftleaf(m, 1, &a[n - m], nw, w);
+    k = 0;
+    for (j = n - m; j > 0; j -= m) {
+        k++;
+        isplt = cfttree(m, j, k, a, nw, w);
+        cftleaf(m, isplt, &a[j - m], nw, w);
+    }
+}
+
+static void rftfsub(int n, double *a, int nc, double *c)
+{
+    int j, k, kk, ks, m;
+    double wkr, wki, xr, xi, yr, yi;
+
+    m = n >> 1;
+    ks = 2 * nc / m;
+    kk = 0;
+    for (j = 2; j < m; j += 2) {
+        k = n - j;
+        kk += ks;
+        wkr = 0.5 - c[nc - kk];
+        wki = c[kk];
+        xr = a[j] - a[k];
+        xi = a[j + 1] + a[k + 1];
+        yr = wkr * xr - wki * xi;
+        yi = wkr * xi + wki * xr;
+        a[j] -= yr;
+        a[j + 1] -= yi;
+        a[k] += yr;
+        a[k + 1] -= yi;
+    }
+}
+
+static void rftbsub(int n, double *a, int nc, double *c)
+{
+    int j, k, kk, ks, m;
+    double wkr, wki, xr, xi, yr, yi;
+
+    m = n >> 1;
+    ks = 2 * nc / m;
+    kk = 0;
+    for (j = 2; j < m; j += 2) {
+        k = n - j;
+        kk += ks;
+        wkr = 0.5 - c[nc - kk];
+        wki = c[kk];
+        xr = a[j] - a[k];
+        xi = a[j + 1] + a[k + 1];
+        yr = wkr * xr + wki * xi;
+        yi = wkr * xi - wki * xr;
+        a[j] -= yr;
+        a[j + 1] -= yi;
+        a[k] += yr;
+        a[k + 1] -= yi;
+    }
+}
+
+#ifdef USE_CDFT_THREADS
+struct cdft_arg_st {
+    int n0;
+    int n;
+    double *a;
+    int nw;
+    double *w;
+};
+typedef struct cdft_arg_st cdft_arg_t;
+
+static void cftrec4_th(int n, double *a, int nw, double *w)
+{
+    int i, idiv4, m, nthread;
+    cdft_thread_t th[4];
+    cdft_arg_t ag[4];
+
+    nthread = 2;
+    idiv4 = 0;
+    m = n >> 1;
+    if (n > CDFT_4THREADS_BEGIN_N) {
+        nthread = 4;
+        idiv4 = 1;
+        m >>= 1;
+    }
+    for (i = 0; i < nthread; i++) {
+        ag[i].n0 = n;
+        ag[i].n = m;
+        ag[i].a = &a[i * m];
+        ag[i].nw = nw;
+        ag[i].w = w;
+        if (i != idiv4) {
+            cdft_thread_create(&th[i], cftrec1_th, &ag[i]);
+        } else {
+            cdft_thread_create(&th[i], cftrec2_th, &ag[i]);
+        }
+    }
+    for (i = 0; i < nthread; i++) {
+        cdft_thread_wait(th[i]);
+    }
+}
+
+static void *cftrec1_th(void *p)
+{
+    int cfttree(int n, int j, int k, double *a, int nw, double *w);
+    void cftleaf(int n, int isplt, double *a, int nw, double *w);
+    void cftmdl1(int n, double *a, double *w);
+    int isplt, j, k, m, n, n0, nw;
+    double *a, *w;
+
+    n0 = ((cdft_arg_t *) p)->n0;
+    n = ((cdft_arg_t *) p)->n;
+    a = ((cdft_arg_t *) p)->a;
+    nw = ((cdft_arg_t *) p)->nw;
+    w = ((cdft_arg_t *) p)->w;
+    m = n0;
+    while (m > 512) {
+        m >>= 2;
+        cftmdl1(m, &a[n - m], &w[nw - (m >> 1)]);
+    }
+    cftleaf(m, 1, &a[n - m], nw, w);
+    k = 0;
+    for (j = n - m; j > 0; j -= m) {
+        k++;
+        isplt = cfttree(m, j, k, a, nw, w);
+        cftleaf(m, isplt, &a[j - m], nw, w);
+    }
+    return (void *) 0;
+}
+
+static void *cftrec2_th(void *p)
+{
+    int cfttree(int n, int j, int k, double *a, int nw, double *w);
+    void cftleaf(int n, int isplt, double *a, int nw, double *w);
+    void cftmdl2(int n, double *a, double *w);
+    int isplt, j, k, m, n, n0, nw;
+    double *a, *w;
+
+    n0 = ((cdft_arg_t *) p)->n0;
+    n = ((cdft_arg_t *) p)->n;
+    a = ((cdft_arg_t *) p)->a;
+    nw = ((cdft_arg_t *) p)->nw;
+    w = ((cdft_arg_t *) p)->w;
+    k = 1;
+    m = n0;
+    while (m > 512) {
+        m >>= 2;
+        k <<= 2;
+        cftmdl2(m, &a[n - m], &w[nw - m]);
+    }
+    cftleaf(m, 0, &a[n - m], nw, w);
+    k >>= 1;
+    for (j = n - m; j > 0; j -= m) {
+        k++;
+        isplt = cfttree(m, j, k, a, nw, w);
+        cftleaf(m, isplt, &a[j - m], nw, w);
+    }
+    return (void *) 0;
+}
+#endif /* USE_CDFT_THREADS */
+
+static void cftbsub(int n, double *a, int *ip, int nw, double *w)
+{
+    if (n > 8) {
+        if (n > 32) {
+            cftb1st(n, a, &w[nw - (n >> 2)]);
+#ifdef USE_CDFT_THREADS
+            if (n > CDFT_THREADS_BEGIN_N) {
+                cftrec4_th(n, a, nw, w);
+            } else
+#endif /* USE_CDFT_THREADS */
+            if (n > 512) {
+                cftrec4(n, a, nw, w);
+            } else if (n > 128) {
+                cftleaf(n, 1, a, nw, w);
+            } else {
+                cftfx41(n, a, nw, w);
+            }
+            bitrv2conj(n, ip, a);
+        } else if (n == 32) {
+            cftf161(a, &w[nw - 8]);
+            bitrv216neg(a);
+        } else {
+            cftf081(a, w);
+            bitrv208neg(a);
+        }
+    } else if (n == 8) {
+        cftb040(a);
+    } else if (n == 4) {
+        cftx020(a);
+    }
+}
+
+static void cftfsub(int n, double *a, int *ip, int nw, double *w)
+{
+    if (n > 8) {
+        if (n > 32) {
+            cftf1st(n, a, &w[nw - (n >> 2)]);
+#ifdef USE_CDFT_THREADS
+            if (n > CDFT_THREADS_BEGIN_N) {
+                cftrec4_th(n, a, nw, w);
+            } else
+#endif /* USE_CDFT_THREADS */
+            if (n > 512) {
+                cftrec4(n, a, nw, w);
+            } else if (n > 128) {
+                cftleaf(n, 1, a, nw, w);
+            } else {
+                cftfx41(n, a, nw, w);
+            }
+            bitrv2(n, ip, a);
+        } else if (n == 32) {
+            cftf161(a, &w[nw - 8]);
+            bitrv216(a);
+        } else {
+            cftf081(a, w);
+            bitrv208(a);
+        }
+    } else if (n == 8) {
+        cftf040(a);
+    } else if (n == 4) {
+        cftx020(a);
+    }
+}
+
+void rdft(int n, int isgn, double *a, int *ip, double *w)
+{
+    int nw, nc;
+    double xi;
+
+    nw = ip[0];
+    if (n > (nw << 2)) {
+        nw = n >> 2;
+        makewt(nw, ip, w);
+    }
+    nc = ip[1];
+    if (n > (nc << 2)) {
+        nc = n >> 2;
+        makect(nc, ip, w + nw);
+    }
+    if (isgn >= 0) {
+        if (n > 4) {
+            cftfsub(n, a, ip, nw, w);
+            rftfsub(n, a, nc, w + nw);
+        } else if (n == 4) {
+            cftfsub(n, a, ip, nw, w);
+        }
+        xi = a[0] - a[1];
+        a[0] += a[1];
+        a[1] = xi;
+    } else {
+        a[1] = 0.5 * (a[0] - a[1]);
+        a[0] -= a[1];
+        if (n > 4) {
+            rftbsub(n, a, nc, w + nw);
+            cftbsub(n, a, ip, nw, w);
+        } else if (n == 4) {
+            cftbsub(n, a, ip, nw, w);
+        }
+    }
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/kaldi-math.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/kaldi-math.cc
new file mode 100644 (file)
index 0000000..bcc0dcc
--- /dev/null
@@ -0,0 +1,51 @@
+// kaldi-native-fbank/csrc/kaldi-math.cc
+//
+// Copyright (c)  2024  Brno University of Technology (authors: Karel Vesely)
+
+// This file is an excerpt from kaldi/src/base/kaldi-math.cc
+
+#include "kaldi-native-fbank/csrc/kaldi-math.h"
+
+#if defined(_POSIX_THREAD_SAFE_FUNCTIONS)
+#include <mutex>  // NOLINT
+#endif
+
+#include <cmath>
+
+namespace knf {
+
+int Rand(struct RandomState *state) {
+#if !defined(_POSIX_THREAD_SAFE_FUNCTIONS)
+  // On Windows and Cygwin, just call Rand()
+  return rand();
+#else
+  if (state) {
+    return rand_r(&(state->seed));
+  } else {
+    static std::mutex _RandMutex;
+    std::lock_guard<std::mutex> lock(_RandMutex);
+    return rand();
+  }
+#endif
+}
+
+RandomState::RandomState() {
+  // we initialize it as Rand() + 27437 instead of just Rand(), because on some
+  // systems, e.g. at the very least Mac OSX Yosemite and later, it seems to be
+  // the case that rand_r when initialized with rand() will give you the exact
+  // same sequence of numbers that rand() will give if you keep calling rand()
+  // after that initial call.  This can cause problems with repeated sequences.
+  // For example if you initialize two RandomState structs one after the other
+  // without calling rand() in between, they would give you the same sequence
+  // offset by one (if we didn't have the "+ 27437" in the code).  27437 is just
+  // a randomly chosen prime number.
+  seed = unsigned(Rand()) + 27437;
+}
+
+void Sqrt(float *in_out, int32_t n) {
+  for (int32_t i = 0; i != n; ++i) {
+    in_out[i] = std::sqrt(in_out[i]);
+  }
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/kaldi-math.h b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/kaldi-math.h
new file mode 100644 (file)
index 0000000..4db3f83
--- /dev/null
@@ -0,0 +1,52 @@
+// kaldi-native-fbank/csrc/kaldi-math.h
+//
+// Copyright (c)  2024  Brno University of Technology (authors: Karel Vesely)
+
+// This file is an excerpt from kaldi/src/base/kaldi-math.h
+
+#ifndef KALDI_NATIVE_FBANK_CSRC_KALDI_MATH_H_
+#define KALDI_NATIVE_FBANK_CSRC_KALDI_MATH_H_
+
+#include <cmath>  // logf, sqrtf, cosf
+#include <cstdint>
+#include <cstdlib>  // RAND_MAX
+
+#ifndef M_PI
+#define M_PI 3.1415926535897932384626433832795
+#endif
+
+#ifndef M_2PI
+#define M_2PI 6.283185307179586476925286766559005
+#endif
+
+#ifndef M_SQRT2
+#define M_SQRT2 1.4142135623730950488016887
+#endif
+
+namespace knf {
+
+inline float Log(float x) { return logf(x); }
+
+// Returns a random integer between 0 and RAND_MAX, inclusive
+int Rand(struct RandomState *state = NULL);
+
+// State for thread-safe random number generator
+struct RandomState {
+  RandomState();
+  unsigned seed;
+};
+
+/// Returns a random number strictly between 0 and 1.
+inline float RandUniform(struct RandomState *state = NULL) {
+  return static_cast<float>((Rand(state) + 1.0) / (RAND_MAX + 2.0));
+}
+
+inline float RandGauss(struct RandomState *state = NULL) {
+  return static_cast<float>(sqrtf(-2 * Log(RandUniform(state))) *
+                            cosf(2 * M_PI * RandUniform(state)));
+}
+
+void Sqrt(float *in_out, int32_t n);
+
+}  // namespace knf
+#endif  // KALDI_NATIVE_FBANK_CSRC_KALDI_MATH_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/log.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/log.cc
new file mode 100644 (file)
index 0000000..7223337
--- /dev/null
@@ -0,0 +1,142 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Stack trace related stuff is from kaldi.
+ * Refer to
+ * https://github.com/kaldi-asr/kaldi/blob/master/src/base/kaldi-error.cc
+ */
+
+#include "kaldi-native-fbank/csrc/log.h"
+
+#ifdef KNF_HAVE_EXECINFO_H
+#include <execinfo.h>  // To get stack trace in error messages.
+#ifdef KNF_HAVE_CXXABI_H
+#include <cxxabi.h>  // For name demangling.
+// Useful to decode the stack trace, but only used if we have execinfo.h
+#endif  // KNF_HAVE_CXXABI_H
+#endif  // KNF_HAVE_EXECINFO_H
+
+#include <stdlib.h>
+
+#include <ctime>
+#include <iomanip>
+#include <string>
+
+namespace knf {
+
+std::string GetDateTimeStr() {
+  std::ostringstream os;
+  std::time_t t = std::time(nullptr);
+  std::tm tm = *std::localtime(&t);
+  os << std::put_time(&tm, "%F %T");  // yyyy-mm-dd hh:mm:ss
+  return os.str();
+}
+
+static bool LocateSymbolRange(const std::string &trace_name, std::size_t *begin,
+                              std::size_t *end) {
+  // Find the first '_' with leading ' ' or '('.
+  *begin = std::string::npos;
+  for (std::size_t i = 1; i < trace_name.size(); ++i) {
+    if (trace_name[i] != '_') {
+      continue;
+    }
+    if (trace_name[i - 1] == ' ' || trace_name[i - 1] == '(') {
+      *begin = i;
+      break;
+    }
+  }
+  if (*begin == std::string::npos) {
+    return false;
+  }
+  *end = trace_name.find_first_of(" +", *begin);
+  return *end != std::string::npos;
+}
+
+#ifdef KNF_HAVE_EXECINFO_H
+static std::string Demangle(const std::string &trace_name) {
+#ifndef KNF_HAVE_CXXABI_H
+  return trace_name;
+#else   // KNF_HAVE_CXXABI_H
+  // Try demangle the symbol. We are trying to support the following formats
+  // produced by different platforms:
+  //
+  // Linux:
+  //   ./kaldi-error-test(_ZN5kaldi13UnitTestErrorEv+0xb) [0x804965d]
+  //
+  // Mac:
+  //   0 server 0x000000010f67614d _ZNK5kaldi13MessageLogger10LogMessageEv + 813
+  //
+  // We want to extract the name e.g., '_ZN5kaldi13UnitTestErrorEv' and
+  // demangle it info a readable name like kaldi::UnitTextError.
+  std::size_t begin, end;
+  if (!LocateSymbolRange(trace_name, &begin, &end)) {
+    return trace_name;
+  }
+  std::string symbol = trace_name.substr(begin, end - begin);
+  int status;
+  char *demangled_name = abi::__cxa_demangle(symbol.c_str(), 0, 0, &status);
+  if (status == 0 && demangled_name != nullptr) {
+    symbol = demangled_name;
+    free(demangled_name);
+  }
+  return trace_name.substr(0, begin) + symbol +
+         trace_name.substr(end, std::string::npos);
+#endif  // KNF_HAVE_CXXABI_H
+}
+#endif  // KNF_HAVE_EXECINFO_H
+
+std::string GetStackTrace() {
+  std::string ans;
+#ifdef KNF_HAVE_EXECINFO_H
+  constexpr const std::size_t kMaxTraceSize = 50;
+  constexpr const std::size_t kMaxTracePrint = 50;  // Must be even.
+                                                    // Buffer for the trace.
+  void *trace[kMaxTraceSize];
+  // Get the trace.
+  std::size_t size = backtrace(trace, kMaxTraceSize);
+  // Get the trace symbols.
+  char **trace_symbol = backtrace_symbols(trace, size);
+  if (trace_symbol == nullptr) return ans;
+
+  // Compose a human-readable backtrace string.
+  ans += "[ Stack-Trace: ]\n";
+  if (size <= kMaxTracePrint) {
+    for (std::size_t i = 0; i < size; ++i) {
+      ans += Demangle(trace_symbol[i]) + "\n";
+    }
+  } else {  // Print out first+last (e.g.) 5.
+    for (std::size_t i = 0; i < kMaxTracePrint / 2; ++i) {
+      ans += Demangle(trace_symbol[i]) + "\n";
+    }
+    ans += ".\n.\n.\n";
+    for (std::size_t i = size - kMaxTracePrint / 2; i < size; ++i) {
+      ans += Demangle(trace_symbol[i]) + "\n";
+    }
+    if (size == kMaxTraceSize)
+      ans += ".\n.\n.\n";  // Stack was too long, probably a bug.
+  }
+
+  // We must free the array of pointers allocated by backtrace_symbols(),
+  // but not the strings themselves.
+  free(trace_symbol);
+#endif  // KNF_HAVE_EXECINFO_H
+  return ans;
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/log.h b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/log.h
new file mode 100644 (file)
index 0000000..bd21cc3
--- /dev/null
@@ -0,0 +1,383 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// The content in this file is copied/modified from
+// https://github.com/k2-fsa/k2/blob/master/k2/csrc/log.h
+#ifndef KALDI_NATIVE_FBANK_CSRC_LOG_H_
+#define KALDI_NATIVE_FBANK_CSRC_LOG_H_
+
+#include <stdio.h>
+
+#include <mutex>  // NOLINT
+#include <sstream>
+#include <string>
+
+namespace knf {
+
+#if KNF_ENABLE_CHECK
+
+#if defined(NDEBUG)
+constexpr bool kDisableDebug = true;
+#else
+constexpr bool kDisableDebug = false;
+#endif
+
+enum class LogLevel {
+  kTrace = 0,
+  kDebug = 1,
+  kInfo = 2,
+  kWarning = 3,
+  kError = 4,
+  kFatal = 5,  // print message and abort the program
+};
+
+// They are used in KNF_LOG(xxx), so their names
+// do not follow the google c++ code style
+//
+// You can use them in the following way:
+//
+//  KNF_LOG(TRACE) << "some message";
+//  KNF_LOG(DEBUG) << "some message";
+#ifndef _MSC_VER
+constexpr LogLevel TRACE = LogLevel::kTrace;
+constexpr LogLevel DEBUG = LogLevel::kDebug;
+constexpr LogLevel INFO = LogLevel::kInfo;
+constexpr LogLevel WARNING = LogLevel::kWarning;
+constexpr LogLevel ERROR = LogLevel::kError;
+constexpr LogLevel FATAL = LogLevel::kFatal;
+#else
+#define TRACE LogLevel::kTrace
+#define DEBUG LogLevel::kDebug
+#define INFO LogLevel::kInfo
+#define WARNING LogLevel::kWarning
+#define ERROR LogLevel::kError
+#define FATAL LogLevel::kFatal
+#endif
+
+std::string GetStackTrace();
+
+/* Return the current log level.
+
+
+   If the current log level is TRACE, then all logged messages are printed out.
+
+   If the current log level is DEBUG, log messages with "TRACE" level are not
+   shown and all other levels are printed out.
+
+   Similarly, if the current log level is INFO, log message with "TRACE" and
+   "DEBUG" are not shown and all other levels are printed out.
+
+   If it is FATAL, then only FATAL messages are shown.
+ */
+inline LogLevel GetCurrentLogLevel() {
+  static LogLevel log_level = INFO;
+  static std::once_flag init_flag;
+  std::call_once(init_flag, []() {
+    const char *env_log_level = std::getenv("KNF_LOG_LEVEL");
+    if (env_log_level == nullptr) return;
+
+    std::string s = env_log_level;
+    if (s == "TRACE")
+      log_level = TRACE;
+    else if (s == "DEBUG")
+      log_level = DEBUG;
+    else if (s == "INFO")
+      log_level = INFO;
+    else if (s == "WARNING")
+      log_level = WARNING;
+    else if (s == "ERROR")
+      log_level = ERROR;
+    else if (s == "FATAL")
+      log_level = FATAL;
+    else
+      fprintf(stderr,
+              "Unknown KNF_LOG_LEVEL: %s"
+              "\nSupported values are: "
+              "TRACE, DEBUG, INFO, WARNING, ERROR, FATAL",
+              s.c_str());
+  });
+  return log_level;
+}
+
+inline bool EnableAbort() {
+  static std::once_flag init_flag;
+  static bool enable_abort = false;
+  std::call_once(init_flag, []() {
+    enable_abort = (std::getenv("KNF_ABORT") != nullptr);
+  });
+  return enable_abort;
+}
+
+class Logger {
+ public:
+  Logger(const char *filename, const char *func_name, uint32_t line_num,
+         LogLevel level)
+      : filename_(filename),
+        func_name_(func_name),
+        line_num_(line_num),
+        level_(level) {
+    cur_level_ = GetCurrentLogLevel();
+    fprintf(stderr, "here\n");
+    switch (level) {
+      case TRACE:
+        if (cur_level_ <= TRACE) fprintf(stderr, "[T] ");
+        break;
+      case DEBUG:
+        if (cur_level_ <= DEBUG) fprintf(stderr, "[D] ");
+        break;
+      case INFO:
+        if (cur_level_ <= INFO) fprintf(stderr, "[I] ");
+        break;
+      case WARNING:
+        if (cur_level_ <= WARNING) fprintf(stderr, "[W] ");
+        break;
+      case ERROR:
+        if (cur_level_ <= ERROR) fprintf(stderr, "[E] ");
+        break;
+      case FATAL:
+        if (cur_level_ <= FATAL) fprintf(stderr, "[F] ");
+        break;
+    }
+
+    if (cur_level_ <= level_) {
+      fprintf(stderr, "%s:%u:%s ", filename, line_num, func_name);
+    }
+  }
+
+  ~Logger() noexcept(false) {
+    static constexpr const char *kErrMsg = R"(
+    Some bad things happened. Please read the above error messages and stack
+    trace. If you are using Python, the following command may be helpful:
+
+      gdb --args python /path/to/your/code.py
+
+    (You can use `gdb` to debug the code. Please consider compiling
+    a debug version of KNF.).
+
+    If you are unable to fix it, please open an issue at:
+
+      https://github.com/csukuangfj/kaldi-native-fbank/issues/new
+    )";
+    fprintf(stderr, "\n");
+    if (level_ == FATAL) {
+      std::string stack_trace = GetStackTrace();
+      if (!stack_trace.empty()) {
+        fprintf(stderr, "\n\n%s\n", stack_trace.c_str());
+      }
+
+      fflush(nullptr);
+
+#ifndef __ANDROID_API__
+      if (EnableAbort()) {
+        // NOTE: abort() will terminate the program immediately without
+        // printing the Python stack backtrace.
+        abort();
+      }
+
+      throw std::runtime_error(kErrMsg);
+#else
+      abort();
+#endif
+    }
+  }
+
+  const Logger &operator<<(bool b) const {
+    if (cur_level_ <= level_) {
+      fprintf(stderr, b ? "true" : "false");
+    }
+    return *this;
+  }
+
+  const Logger &operator<<(int8_t i) const {
+    if (cur_level_ <= level_) fprintf(stderr, "%d", i);
+    return *this;
+  }
+
+  const Logger &operator<<(const char *s) const {
+    if (cur_level_ <= level_) fprintf(stderr, "%s", s);
+    return *this;
+  }
+
+  const Logger &operator<<(int32_t i) const {
+    if (cur_level_ <= level_) fprintf(stderr, "%d", i);
+    return *this;
+  }
+
+  const Logger &operator<<(uint32_t i) const {
+    if (cur_level_ <= level_) fprintf(stderr, "%u", i);
+    return *this;
+  }
+
+  const Logger &operator<<(uint64_t i) const {
+    if (cur_level_ <= level_)
+      fprintf(stderr, "%llu", (long long unsigned int)i);  // NOLINT
+    return *this;
+  }
+
+  const Logger &operator<<(int64_t i) const {
+    if (cur_level_ <= level_)
+      fprintf(stderr, "%lli", (long long int)i);  // NOLINT
+    return *this;
+  }
+
+  const Logger &operator<<(float f) const {
+    if (cur_level_ <= level_) fprintf(stderr, "%f", f);
+    return *this;
+  }
+
+  const Logger &operator<<(double d) const {
+    if (cur_level_ <= level_) fprintf(stderr, "%f", d);
+    return *this;
+  }
+
+  template <typename T>
+  const Logger &operator<<(const T &t) const {
+    // require T overloads operator<<
+    std::ostringstream os;
+    os << t;
+    return *this << os.str().c_str();
+  }
+
+  // specialization to fix compile error: `stringstream << nullptr` is ambiguous
+  const Logger &operator<<(const std::nullptr_t &null) const {
+    if (cur_level_ <= level_) *this << "(null)";
+    return *this;
+  }
+
+ private:
+  const char *filename_;
+  const char *func_name_;
+  uint32_t line_num_;
+  LogLevel level_;
+  LogLevel cur_level_;
+};
+#endif  // KNF_ENABLE_CHECK
+
+class Voidifier {
+ public:
+#if KNF_ENABLE_CHECK
+  void operator&(const Logger &) const {}
+#endif
+};
+#if !defined(KNF_ENABLE_CHECK)
+template <typename T>
+const Voidifier &operator<<(const Voidifier &v, T &&) {
+  return v;
+}
+#endif
+
+}  // namespace knf
+
+#define KNF_STATIC_ASSERT(x) static_assert(x, "")
+
+#ifdef KNF_ENABLE_CHECK
+
+#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) || \
+    defined(__PRETTY_FUNCTION__)
+// for clang and GCC
+#define KNF_FUNC __PRETTY_FUNCTION__
+#else
+// for other compilers
+#define KNF_FUNC __func__
+#endif
+
+#define KNF_CHECK(x)                                                  \
+  (x) ? (void)0                                                       \
+      : ::knf::Voidifier() &                                          \
+            ::knf::Logger(__FILE__, KNF_FUNC, __LINE__, ::knf::FATAL) \
+                << "Check failed: " << #x << " "
+
+// WARNING: x and y may be evaluated multiple times, but this happens only
+// when the check fails. Since the program aborts if it fails, we don't think
+// the extra evaluation of x and y matters.
+//
+// CAUTION: we recommend the following use case:
+//
+//      auto x = Foo();
+//      auto y = Bar();
+//      KNF_CHECK_EQ(x, y) << "Some message";
+//
+//  And please avoid
+//
+//      KNF_CHECK_EQ(Foo(), Bar());
+//
+//  if `Foo()` or `Bar()` causes some side effects, e.g., changing some
+//  local static variables or global variables.
+#define _KNF_CHECK_OP(x, y, op)                                              \
+  ((x)op(y)) ? (void)0                                                       \
+             : ::knf::Voidifier() &                                          \
+                   ::knf::Logger(__FILE__, KNF_FUNC, __LINE__, ::knf::FATAL) \
+                       << "Check failed: " << #x << " " << #op << " " << #y  \
+                       << " (" << (x) << " vs. " << (y) << ") "
+
+#define KNF_CHECK_EQ(x, y) _KNF_CHECK_OP(x, y, ==)
+#define KNF_CHECK_NE(x, y) _KNF_CHECK_OP(x, y, !=)
+#define KNF_CHECK_LT(x, y) _KNF_CHECK_OP(x, y, <)
+#define KNF_CHECK_LE(x, y) _KNF_CHECK_OP(x, y, <=)
+#define KNF_CHECK_GT(x, y) _KNF_CHECK_OP(x, y, >)
+#define KNF_CHECK_GE(x, y) _KNF_CHECK_OP(x, y, >=)
+
+#define KNF_LOG(x) ::knf::Logger(__FILE__, KNF_FUNC, __LINE__, ::knf::x)
+
+// ------------------------------------------------------------
+//       For debug check
+// ------------------------------------------------------------
+// If you define the macro "-D NDEBUG" while compiling kaldi-native-fbank,
+// the following macros are in fact empty and does nothing.
+
+#define KNF_DCHECK(x) ::knf::kDisableDebug ? (void)0 : KNF_CHECK(x)
+
+#define KNF_DCHECK_EQ(x, y) ::knf::kDisableDebug ? (void)0 : KNF_CHECK_EQ(x, y)
+
+#define KNF_DCHECK_NE(x, y) ::knf::kDisableDebug ? (void)0 : KNF_CHECK_NE(x, y)
+
+#define KNF_DCHECK_LT(x, y) ::knf::kDisableDebug ? (void)0 : KNF_CHECK_LT(x, y)
+
+#define KNF_DCHECK_LE(x, y) ::knf::kDisableDebug ? (void)0 : KNF_CHECK_LE(x, y)
+
+#define KNF_DCHECK_GT(x, y) ::knf::kDisableDebug ? (void)0 : KNF_CHECK_GT(x, y)
+
+#define KNF_DCHECK_GE(x, y) ::knf::kDisableDebug ? (void)0 : KNF_CHECK_GE(x, y)
+
+#define KNF_DLOG(x) \
+  ::knf::kDisableDebug ? (void)0 : ::knf::Voidifier() & KNF_LOG(x)
+
+#else
+
+#define KNF_CHECK(x) ::knf::Voidifier()
+#define KNF_LOG(x) ::knf::Voidifier()
+
+#define KNF_CHECK_EQ(x, y) ::knf::Voidifier()
+#define KNF_CHECK_NE(x, y) ::knf::Voidifier()
+#define KNF_CHECK_LT(x, y) ::knf::Voidifier()
+#define KNF_CHECK_LE(x, y) ::knf::Voidifier()
+#define KNF_CHECK_GT(x, y) ::knf::Voidifier()
+#define KNF_CHECK_GE(x, y) ::knf::Voidifier()
+
+#define KNF_DCHECK(x) ::knf::Voidifier()
+#define KNF_DLOG(x) ::knf::Voidifier()
+#define KNF_DCHECK_EQ(x, y) ::knf::Voidifier()
+#define KNF_DCHECK_NE(x, y) ::knf::Voidifier()
+#define KNF_DCHECK_LT(x, y) ::knf::Voidifier()
+#define KNF_DCHECK_LE(x, y) ::knf::Voidifier()
+#define KNF_DCHECK_GT(x, y) ::knf::Voidifier()
+#define KNF_DCHECK_GE(x, y) ::knf::Voidifier()
+
+#endif  // KNF_CHECK_NE
+
+#endif  // KALDI_NATIVE_FBANK_CSRC_LOG_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/mel-computations.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/mel-computations.cc
new file mode 100644 (file)
index 0000000..c083a56
--- /dev/null
@@ -0,0 +1,422 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This file is copied/modified from kaldi/src/feat/mel-computations.cc
+
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+
+#include <stdio.h>
+
+#include <algorithm>
+#include <sstream>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/kaldi-math.h"
+#include "kaldi-native-fbank/csrc/log.h"
+
+namespace knf {
+
+std::ostream &operator<<(std::ostream &os, const MelBanksOptions &opts) {
+  os << opts.ToString();
+  return os;
+}
+
+float MelBanks::VtlnWarpFreq(
+    float vtln_low_cutoff,  // upper+lower frequency cutoffs for VTLN.
+    float vtln_high_cutoff,
+    float low_freq,  // upper+lower frequency cutoffs in mel computation
+    float high_freq, float vtln_warp_factor, float freq) {
+  /// This computes a VTLN warping function that is not the same as HTK's one,
+  /// but has similar inputs (this function has the advantage of never producing
+  /// empty bins).
+
+  /// This function computes a warp function F(freq), defined between low_freq
+  /// and high_freq inclusive, with the following properties:
+  ///  F(low_freq) == low_freq
+  ///  F(high_freq) == high_freq
+  /// The function is continuous and piecewise linear with two inflection
+  ///   points.
+  /// The lower inflection point (measured in terms of the unwarped
+  ///  frequency) is at frequency l, determined as described below.
+  /// The higher inflection point is at a frequency h, determined as
+  ///   described below.
+  /// If l <= f <= h, then F(f) = f/vtln_warp_factor.
+  /// If the higher inflection point (measured in terms of the unwarped
+  ///   frequency) is at h, then max(h, F(h)) == vtln_high_cutoff.
+  ///   Since (by the last point) F(h) == h/vtln_warp_factor, then
+  ///   max(h, h/vtln_warp_factor) == vtln_high_cutoff, so
+  ///   h = vtln_high_cutoff / max(1, 1/vtln_warp_factor).
+  ///     = vtln_high_cutoff * min(1, vtln_warp_factor).
+  /// If the lower inflection point (measured in terms of the unwarped
+  ///   frequency) is at l, then min(l, F(l)) == vtln_low_cutoff
+  ///   This implies that l = vtln_low_cutoff / min(1, 1/vtln_warp_factor)
+  ///                       = vtln_low_cutoff * max(1, vtln_warp_factor)
+
+  if (freq < low_freq || freq > high_freq)
+    return freq;  // in case this gets called
+  // for out-of-range frequencies, just return the freq.
+
+  KNF_CHECK_GT(vtln_low_cutoff, low_freq);
+  KNF_CHECK_LT(vtln_high_cutoff, high_freq);
+
+  float one = 1.0f;
+  float l = vtln_low_cutoff * std::max(one, vtln_warp_factor);
+  float h = vtln_high_cutoff * std::min(one, vtln_warp_factor);
+  float scale = 1.0f / vtln_warp_factor;
+  float Fl = scale * l;  // F(l);
+  float Fh = scale * h;  // F(h);
+  KNF_CHECK(l > low_freq && h < high_freq);
+  // slope of left part of the 3-piece linear function
+  float scale_left = (Fl - low_freq) / (l - low_freq);
+  // [slope of center part is just "scale"]
+
+  // slope of right part of the 3-piece linear function
+  float scale_right = (high_freq - Fh) / (high_freq - h);
+
+  if (freq < l) {
+    return low_freq + scale_left * (freq - low_freq);
+  } else if (freq < h) {
+    return scale * freq;
+  } else {  // freq >= h
+    return high_freq + scale_right * (freq - high_freq);
+  }
+}
+
+float MelBanks::VtlnWarpMelFreq(
+    float vtln_low_cutoff,  // upper+lower frequency cutoffs for VTLN.
+    float vtln_high_cutoff,
+    float low_freq,  // upper+lower frequency cutoffs in mel computation
+    float high_freq, float vtln_warp_factor, float mel_freq) {
+  return MelScale(VtlnWarpFreq(vtln_low_cutoff, vtln_high_cutoff, low_freq,
+                               high_freq, vtln_warp_factor,
+                               InverseMelScale(mel_freq)));
+}
+
+MelBanks::MelBanks(const MelBanksOptions &opts,
+                   const FrameExtractionOptions &frame_opts,
+                   float vtln_warp_factor) {
+  if (opts.is_librosa) {
+    InitLibrosaMelBanks(opts, frame_opts, vtln_warp_factor);
+  } else {
+    InitKaldiMelBanks(opts, frame_opts, vtln_warp_factor);
+  }
+}
+
+void MelBanks::InitKaldiMelBanks(const MelBanksOptions &opts,
+                                 const FrameExtractionOptions &frame_opts,
+                                 float vtln_warp_factor) {
+  htk_mode_ = opts.htk_mode;
+  int32_t num_bins = opts.num_bins;
+  if (num_bins < 3) {
+    KNF_LOG(FATAL) << "Must have at least 3 mel bins";
+  }
+
+  float sample_freq = frame_opts.samp_freq;
+  int32_t window_length_padded = frame_opts.PaddedWindowSize();
+  KNF_CHECK_EQ(window_length_padded % 2, 0);
+
+  int32_t num_fft_bins = window_length_padded / 2;
+  float nyquist = 0.5f * sample_freq;
+
+  float low_freq = opts.low_freq, high_freq;
+  if (opts.high_freq > 0.0f) {
+    high_freq = opts.high_freq;
+  } else {
+    high_freq = nyquist + opts.high_freq;
+  }
+
+  if (low_freq < 0.0f || low_freq >= nyquist || high_freq <= 0.0f ||
+      high_freq > nyquist || high_freq <= low_freq) {
+    KNF_LOG(FATAL) << "Bad values in options: low-freq " << low_freq
+                   << " and high-freq " << high_freq << " vs. nyquist "
+                   << nyquist;
+  }
+
+  float fft_bin_width = sample_freq / window_length_padded;
+  // fft-bin width [think of it as Nyquist-freq / half-window-length]
+
+  float mel_low_freq = MelScale(low_freq);
+  float mel_high_freq = MelScale(high_freq);
+
+  debug_ = opts.debug_mel;
+
+  // divide by num_bins+1 in next line because of end-effects where the bins
+  // spread out to the sides.
+  float mel_freq_delta = (mel_high_freq - mel_low_freq) / (num_bins + 1);
+
+  float vtln_low = opts.vtln_low, vtln_high = opts.vtln_high;
+  if (vtln_high < 0.0f) {
+    vtln_high += nyquist;
+  }
+
+  if (vtln_warp_factor != 1.0f &&
+      (vtln_low < 0.0f || vtln_low <= low_freq || vtln_low >= high_freq ||
+       vtln_high <= 0.0f || vtln_high >= high_freq || vtln_high <= vtln_low)) {
+    KNF_LOG(FATAL) << "Bad values in options: vtln-low " << vtln_low
+                   << " and vtln-high " << vtln_high << ", versus "
+                   << "low-freq " << low_freq << " and high-freq " << high_freq;
+  }
+
+  bins_.resize(num_bins);
+
+  for (int32_t bin = 0; bin < num_bins; ++bin) {
+    float left_mel = mel_low_freq + bin * mel_freq_delta,
+          center_mel = mel_low_freq + (bin + 1) * mel_freq_delta,
+          right_mel = mel_low_freq + (bin + 2) * mel_freq_delta;
+
+    if (vtln_warp_factor != 1.0f) {
+      left_mel = VtlnWarpMelFreq(vtln_low, vtln_high, low_freq, high_freq,
+                                 vtln_warp_factor, left_mel);
+      center_mel = VtlnWarpMelFreq(vtln_low, vtln_high, low_freq, high_freq,
+                                   vtln_warp_factor, center_mel);
+      right_mel = VtlnWarpMelFreq(vtln_low, vtln_high, low_freq, high_freq,
+                                  vtln_warp_factor, right_mel);
+    }
+
+    // this_bin will be a vector of coefficients that is only
+    // nonzero where this mel bin is active.
+    std::vector<float> this_bin(num_fft_bins);
+
+    int32_t first_index = -1, last_index = -1;
+    for (int32_t i = 0; i < num_fft_bins; ++i) {
+      float freq = (fft_bin_width * i);  // Center frequency of this fft
+                                         // bin.
+      float mel = MelScale(freq);
+      if (mel > left_mel && mel < right_mel) {
+        float weight;
+        if (mel <= center_mel) {
+          weight = (mel - left_mel) / (center_mel - left_mel);
+        } else {
+          weight = (right_mel - mel) / (right_mel - center_mel);
+        }
+        this_bin[i] = weight;
+        if (first_index == -1) {
+          first_index = i;
+        }
+        last_index = i;
+      }
+    }
+    KNF_CHECK(first_index != -1 && last_index >= first_index &&
+              "You may have set num_mel_bins too large.");
+
+    bins_[bin].first = first_index;
+    int32_t size = last_index + 1 - first_index;
+    bins_[bin].second.insert(bins_[bin].second.end(),
+                             this_bin.begin() + first_index,
+                             this_bin.begin() + first_index + size);
+
+    // Replicate a bug in HTK, for testing purposes.
+    if (opts.htk_mode && bin == 0 && mel_low_freq != 0.0f) {
+      bins_[bin].second[0] = 0.0;
+    }
+  }  // for (int32_t bin = 0; bin < num_bins; ++bin) {
+
+  if (debug_) {
+    std::ostringstream os;
+    for (size_t i = 0; i < bins_.size(); i++) {
+      os << "bin " << i << ", offset = " << bins_[i].first << ", vec = ";
+      for (auto k : bins_[i].second) os << k << ", ";
+      os << "\n";
+    }
+    KNF_LOG(INFO) << os.str();
+  }
+}
+
+void MelBanks::InitLibrosaMelBanks(const MelBanksOptions &opts,
+                                   const FrameExtractionOptions &frame_opts,
+                                   float vtln_warp_factor) {
+  htk_mode_ = opts.htk_mode;
+  int32_t num_bins = opts.num_bins;
+  if (num_bins < 3) {
+    KNF_LOG(FATAL) << "Must have at least 3 mel bins";
+  }
+
+  float sample_freq = frame_opts.samp_freq;
+  int32_t window_length_padded = frame_opts.PaddedWindowSize();
+  KNF_CHECK_EQ(window_length_padded % 2, 0);
+
+  int32_t num_fft_bins = window_length_padded / 2;
+  float nyquist = 0.5f * sample_freq;
+
+  float low_freq = opts.low_freq, high_freq;
+  if (opts.high_freq > 0.0f) {
+    high_freq = opts.high_freq;
+  } else {
+    high_freq = nyquist + opts.high_freq;
+  }
+
+  if (low_freq < 0.0f || low_freq >= nyquist || high_freq <= 0.0f ||
+      high_freq > nyquist || high_freq <= low_freq) {
+    KNF_LOG(FATAL) << "Bad values in options: low-freq " << low_freq
+                   << " and high-freq " << high_freq << " vs. nyquist "
+                   << nyquist;
+  }
+
+  float fft_bin_width = sample_freq / window_length_padded;
+
+  float mel_low_freq = MelScaleSlaney(low_freq);
+  float mel_high_freq = MelScaleSlaney(high_freq);
+
+  debug_ = opts.debug_mel;
+
+  // divide by num_bins+1 in next line because of end-effects where the bins
+  // spread out to the sides.
+  float mel_freq_delta = (mel_high_freq - mel_low_freq) / (num_bins + 1);
+
+  bool slaney_norm = false;
+  if (!opts.norm.empty()) {
+    if (opts.norm != "slaney") {
+      KNF_LOG(FATAL) << "Unsupported norm: " << opts.norm;
+    }
+    slaney_norm = true;
+  }
+
+  bins_.resize(num_bins);
+  for (int32_t bin = 0; bin < num_bins; ++bin) {
+    float left_mel = mel_low_freq + bin * mel_freq_delta;
+    float center_mel = mel_low_freq + (bin + 1) * mel_freq_delta;
+    float right_mel = mel_low_freq + (bin + 2) * mel_freq_delta;
+
+    float left_hz = InverseMelScaleSlaney(left_mel);
+    float center_hz = InverseMelScaleSlaney(center_mel);
+    float right_hz = InverseMelScaleSlaney(right_mel);
+
+    // this_bin will be a vector of coefficients that is only
+    // nonzero where this mel bin is active.
+    //
+    // It is not an error to use num_fft_bins + 1 here. It is different
+    // from Kaldi.
+    std::vector<float> this_bin(num_fft_bins + 1);
+
+    int32_t first_index = -1, last_index = -1;
+    for (int32_t i = 0; i < num_fft_bins + 1; ++i) {
+      float hz = (fft_bin_width * i);  // Center frequency of this fft bin.
+      if (hz > left_hz && hz < right_hz) {
+        float weight;
+        if (hz <= center_hz) {
+          weight = (hz - left_hz) / (center_hz - left_hz);
+        } else {
+          weight = (right_hz - hz) / (right_hz - center_hz);
+        }
+
+        if (slaney_norm) {
+          weight *= 2 / (right_hz - left_hz);
+        }
+
+        this_bin[i] = weight;
+        if (first_index == -1) {
+          first_index = i;
+        }
+
+        last_index = i;
+      }
+    }  // for (int32_t i = 0; i < num_fft_bins + 1; ++i)
+
+    KNF_CHECK(first_index != -1 && last_index >= first_index &&
+              "You may have set num_mel_bins too large.");
+
+    bins_[bin].first = first_index;
+    int32_t size = last_index + 1 - first_index;
+    bins_[bin].second.insert(bins_[bin].second.end(),
+                             this_bin.begin() + first_index,
+                             this_bin.begin() + first_index + size);
+  }  // for (int32_t bin = 0; bin < num_bins; ++bin)
+
+  if (debug_) {
+    std::ostringstream os;
+    for (size_t i = 0; i < bins_.size(); i++) {
+      os << "bin " << i << ", offset = " << bins_[i].first << ", vec = ";
+      for (auto k : bins_[i].second) os << k << ", ";
+      os << "\n";
+    }
+    fprintf(stderr, "%s\n", os.str().c_str());
+  }
+}
+
+MelBanks::MelBanks(const float *weights, int32_t num_rows, int32_t num_cols)
+    : debug_(false), htk_mode_(false) {
+  bins_.resize(num_rows);
+  for (int32_t bin = 0; bin < num_rows; ++bin) {
+    const float *this_bin = weights + bin * num_cols;
+
+    int32_t first_index = -1, last_index = -1;
+
+    for (int32_t i = 0; i < num_cols; ++i) {
+      if (this_bin[i] == 0) {
+        continue;
+      }
+      if (first_index == -1) first_index = i;
+      last_index = i;
+    }
+
+    KNF_CHECK(first_index != -1 && last_index >= first_index &&
+              "You have an incorrect weight matrix.");
+
+    bins_[bin].first = first_index;
+    int32_t size = last_index + 1 - first_index;
+
+    bins_[bin].second.insert(bins_[bin].second.end(), this_bin + first_index,
+                             this_bin + first_index + size);
+  }
+}
+
+// "power_spectrum" contains fft energies.
+void MelBanks::Compute(const float *power_spectrum,
+                       float *mel_energies_out) const {
+  int32_t num_bins = bins_.size();
+
+  for (int32_t i = 0; i < num_bins; i++) {
+    int32_t offset = bins_[i].first;
+    const auto &v = bins_[i].second;
+    float energy = 0;
+    for (int32_t k = 0; k != v.size(); ++k) {
+      energy += v[k] * power_spectrum[k + offset];
+    }
+
+    // HTK-like flooring- for testing purposes (we prefer dither)
+    if (htk_mode_ && energy < 1.0) {
+      energy = 1.0;
+    }
+
+    mel_energies_out[i] = energy;
+
+    // The following assert was added due to a problem with OpenBlas that
+    // we had at one point (it was a bug in that library).  Just to detect
+    // it early.
+    KNF_CHECK_EQ(energy, energy);  // check that energy is not nan
+  }
+
+  if (debug_) {
+    fprintf(stderr, "MEL BANKS:\n");
+    for (int32_t i = 0; i < num_bins; i++)
+      fprintf(stderr, " %f", mel_energies_out[i]);
+    fprintf(stderr, "\n");
+  }
+}
+
+void ComputeLifterCoeffs(float Q, std::vector<float> *coeffs) {
+  // Compute liftering coefficients (scaling on cepstral coeffs)
+  // coeffs are numbered slightly differently from HTK: the zeroth
+  // index is C0, which is not affected.
+  for (int32_t i = 0; i != static_cast<int32_t>(coeffs->size()); ++i) {
+    (*coeffs)[i] = 1.0 + 0.5 * Q * sin(M_PI * i / Q);
+  }
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/mel-computations.h b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/mel-computations.h
new file mode 100644 (file)
index 0000000..c81ce97
--- /dev/null
@@ -0,0 +1,182 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// This file is copied/modified from kaldi/src/feat/mel-computations.h
+#ifndef KALDI_NATIVE_FBANK_CSRC_MEL_COMPUTATIONS_H_
+#define KALDI_NATIVE_FBANK_CSRC_MEL_COMPUTATIONS_H_
+
+#include <cmath>
+#include <cstdint>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-window.h"
+
+namespace knf {
+struct FrameExtractionOptions;
+
+struct MelBanksOptions {
+  int32_t num_bins = 25;  // e.g. 25; number of triangular bins
+  float low_freq = 20;    // e.g. 20; lower frequency cutoff
+
+  // an upper frequency cutoff; 0 -> no cutoff, negative
+  // ->added to the Nyquist frequency to get the cutoff.
+  float high_freq = 0;
+
+  float vtln_low = 100;  // vtln lower cutoff of warping function.
+
+  // vtln upper cutoff of warping function: if negative, added
+  // to the Nyquist frequency to get the cutoff.
+  float vtln_high = -500;
+
+  bool debug_mel = false;
+  // htk_mode is a "hidden" config, it does not show up on command line.
+  // Enables more exact compatibility with HTK, for testing purposes.  Affects
+  // mel-energy flooring and reproduces a bug in HTK.
+  bool htk_mode = false;
+
+  // Note that if you set is_librosa, you probably need to set
+  // low_freq to 0.
+  // Please see
+  // https://librosa.org/doc/main/generated/librosa.filters.mel.html
+  bool is_librosa = false;
+
+  // used only when is_librosa=true
+  // Possible values: "", slaney. We don't support a numeric value here, but
+  // it can be added on demand.
+  // See https://librosa.org/doc/main/generated/librosa.filters.mel.html
+  std::string norm = "slaney";
+
+  std::string ToString() const {
+    std::ostringstream os;
+    os << "num_bins: " << num_bins << "\n";
+    os << "low_freq: " << low_freq << "\n";
+    os << "high_freq: " << high_freq << "\n";
+    os << "vtln_low: " << vtln_low << "\n";
+    os << "vtln_high: " << vtln_high << "\n";
+    os << "debug_mel: " << debug_mel << "\n";
+    os << "htk_mode: " << htk_mode << "\n";
+    os << "is_librosa: " << is_librosa << "\n";
+    os << "norm: " << norm << "\n";
+    return os.str();
+  }
+};
+
+std::ostream &operator<<(std::ostream &os, const MelBanksOptions &opts);
+
+class MelBanks {
+ public:
+  // see also https://en.wikipedia.org/wiki/Mel_scale
+  // htk, mel to hz
+  static inline float InverseMelScale(float mel_freq) {
+    return 700.0f * (expf(mel_freq / 1127.0f) - 1.0f);
+  }
+
+  // htk, hz to mel
+  static inline float MelScale(float freq) {
+    return 1127.0f * logf(1.0f + freq / 700.0f);
+  }
+
+  // slaney, mel to hz
+  static inline float InverseMelScaleSlaney(float mel_freq) {
+    if (mel_freq <= 15) {
+      return 200.0f / 3 * mel_freq;
+    }
+
+    // return 1000 * expf((mel_freq - 15) * logf(6.4f) / 27);
+
+    // Note: log(6.4)/27 = 0.06875177742094911
+
+    return 1000 * expf((mel_freq - 15) * 0.06875177742094911f);
+  }
+
+  // slaney, hz to mel
+  static inline float MelScaleSlaney(float freq) {
+    if (freq <= 1000) {
+      return freq * 3 / 200.0f;
+    }
+
+    // return 15 + 27 * logf(freq / 1000) / logf(6.4f)
+    //
+    // Note: 27/log(6.4) = 14.545078505785561
+
+    return 15 + 14.545078505785561f * logf(freq / 1000);
+  }
+
+  static float VtlnWarpFreq(
+      float vtln_low_cutoff,
+      float vtln_high_cutoff,  // discontinuities in warp func
+      float low_freq,
+      float high_freq,  // upper+lower frequency cutoffs in
+      // the mel computation
+      float vtln_warp_factor, float freq);
+
+  static float VtlnWarpMelFreq(float vtln_low_cutoff, float vtln_high_cutoff,
+                               float low_freq, float high_freq,
+                               float vtln_warp_factor, float mel_freq);
+
+  // TODO(fangjun): Remove vtln_warp_factor
+  MelBanks(const MelBanksOptions &opts,
+           const FrameExtractionOptions &frame_opts, float vtln_warp_factor);
+
+  // Initialize with a 2-d weights matrix
+  // @param weights Pointer to the start address of the matrix
+  // @param num_rows It equls to number of mel bins
+  // @param num_cols It equals to (number of fft bins)/2+1
+  MelBanks(const float *weights, int32_t num_rows, int32_t num_cols);
+
+  /// Compute Mel energies (note: not log energies).
+  /// At input, "fft_energies" contains the FFT energies (not log).
+  ///
+  /// @param fft_energies 1-D array of size num_fft_bins/2+1
+  /// @param mel_energies_out  1-D array of size num_mel_bins
+  void Compute(const float *fft_energies, float *mel_energies_out) const;
+
+  int32_t NumBins() const { return bins_.size(); }
+
+ private:
+  // for kaldi-compatible
+  void InitKaldiMelBanks(const MelBanksOptions &opts,
+                         const FrameExtractionOptions &frame_opts,
+                         float vtln_warp_factor);
+
+  // for librosa-compatible
+  // See https://librosa.org/doc/main/generated/librosa.filters.mel.html
+  void InitLibrosaMelBanks(const MelBanksOptions &opts,
+                           const FrameExtractionOptions &frame_opts,
+                           float vtln_warp_factor);
+
+ private:
+  // the "bins_" vector is a vector, one for each bin, of a pair:
+  // (the first nonzero fft-bin), (the vector of weights).
+  std::vector<std::pair<int32_t, std::vector<float>>> bins_;
+
+  // TODO(fangjun): Remove debug_ and htk_mode_
+  bool debug_ = false;
+  bool htk_mode_ = false;
+};
+
+// Compute liftering coefficients (scaling on cepstral coeffs)
+// coeffs are numbered slightly differently from HTK: the zeroth
+// index is C0, which is not affected.
+void ComputeLifterCoeffs(float Q, std::vector<float> *coeffs);
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_CSRC_MEL_COMPUTATIONS_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/online-feature.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/online-feature.cc
new file mode 100644 (file)
index 0000000..13c4369
--- /dev/null
@@ -0,0 +1,169 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// The content in this file is copied/modified from
+// This file is copied/modified from kaldi/src/feat/online-feature.cc
+
+#include "kaldi-native-fbank/csrc/online-feature.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <utility>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/log.h"
+
+namespace knf {
+
+RecyclingVector::RecyclingVector(int32_t items_to_hold)
+    : items_to_hold_(items_to_hold == 0 ? -1 : items_to_hold),
+      first_available_index_(0) {}
+
+const float *RecyclingVector::At(int32_t index) const {
+  if (index < first_available_index_) {
+    KNF_LOG(FATAL) << "Attempted to retrieve feature vector that was "
+                      "already removed by the RecyclingVector (index = "
+                   << index << "; "
+                   << "first_available_index = " << first_available_index_
+                   << "; "
+                   << "size = " << Size() << ")";
+  }
+  // 'at' does size checking.
+  return items_.at(index - first_available_index_).data();
+}
+
+void RecyclingVector::PushBack(std::vector<float> item) {
+  // Note: -1 is a larger number when treated as unsigned
+  if (items_.size() == static_cast<size_t>(items_to_hold_)) {
+    items_.pop_front();
+    ++first_available_index_;
+  }
+  items_.push_back(std::move(item));
+}
+
+int32_t RecyclingVector::Size() const {
+  return first_available_index_ + static_cast<int32_t>(items_.size());
+}
+
+// discard the first n frames
+void RecyclingVector::Pop(int32_t n) {
+  for (int32_t i = 0; i < n && !items_.empty(); ++i) {
+    items_.pop_front();
+    ++first_available_index_;
+  }
+}
+
+template <class C>
+OnlineGenericBaseFeature<C>::OnlineGenericBaseFeature(
+    const typename C::Options &opts)
+    : computer_(opts),
+      window_function_(computer_.GetFrameOptions()),
+      input_finished_(false),
+      waveform_offset_(0) {}
+
+template <class C>
+void OnlineGenericBaseFeature<C>::AcceptWaveform(float sampling_rate,
+                                                 const float *waveform,
+                                                 int32_t n) {
+  if (n == 0) {
+    return;  // Nothing to do.
+  }
+
+  if (input_finished_) {
+    KNF_LOG(FATAL) << "AcceptWaveform called after InputFinished() was called.";
+  }
+
+  KNF_CHECK_EQ(sampling_rate, computer_.GetFrameOptions().samp_freq);
+
+  waveform_remainder_.insert(waveform_remainder_.end(), waveform, waveform + n);
+
+  ComputeFeatures();
+}
+
+template <class C>
+void OnlineGenericBaseFeature<C>::InputFinished() {
+  input_finished_ = true;
+  ComputeFeatures();
+}
+
+template <class C>
+void OnlineGenericBaseFeature<C>::ComputeFeatures() {
+  const FrameExtractionOptions &frame_opts = computer_.GetFrameOptions();
+
+  int64_t num_samples_total = waveform_offset_ + waveform_remainder_.size();
+
+  int32_t num_frames_old = features_.Size();
+
+  int32_t num_frames_new =
+      NumFrames(num_samples_total, frame_opts, input_finished_);
+
+  KNF_CHECK_GE(num_frames_new, num_frames_old);
+
+  // note: this online feature-extraction code does not support VTLN.
+  float vtln_warp = 1.0;
+
+  std::vector<float> window;
+  bool need_raw_log_energy = computer_.NeedRawLogEnergy();
+
+  for (int32_t frame = num_frames_old; frame < num_frames_new; ++frame) {
+    std::fill(window.begin(), window.end(), 0);
+    float raw_log_energy = 0.0;
+    ExtractWindow(waveform_offset_, waveform_remainder_, frame, frame_opts,
+                  window_function_, &window,
+                  need_raw_log_energy ? &raw_log_energy : nullptr);
+
+    std::vector<float> this_feature(computer_.Dim());
+
+    computer_.Compute(raw_log_energy, vtln_warp, &window, this_feature.data());
+    features_.PushBack(std::move(this_feature));
+  }
+
+  // OK, we will now discard any portion of the signal that will not be
+  // necessary to compute frames in the future.
+  int64_t first_sample_of_next_frame =
+      FirstSampleOfFrame(num_frames_new, frame_opts);
+
+  int32_t samples_to_discard = first_sample_of_next_frame - waveform_offset_;
+
+  if (samples_to_discard > 0) {
+    // discard the leftmost part of the waveform that we no longer need.
+    int32_t new_num_samples =
+        static_cast<int32_t>(waveform_remainder_.size()) - samples_to_discard;
+
+    if (new_num_samples <= 0) {
+      // odd, but we'll try to handle it.
+      waveform_offset_ += waveform_remainder_.size();
+      waveform_remainder_.resize(0);
+    } else {
+      std::vector<float> new_remainder(new_num_samples);
+
+      std::copy(waveform_remainder_.begin() + samples_to_discard,
+                waveform_remainder_.end(), new_remainder.begin());
+      waveform_offset_ += samples_to_discard;
+
+      waveform_remainder_.swap(new_remainder);
+    }
+  }
+}
+
+template class OnlineGenericBaseFeature<FbankComputer>;
+template class OnlineGenericBaseFeature<MfccComputer>;
+template class OnlineGenericBaseFeature<WhisperFeatureComputer>;
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/online-feature.h b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/online-feature.h
new file mode 100644 (file)
index 0000000..3d42fd4
--- /dev/null
@@ -0,0 +1,153 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// The content in this file is copied/modified from
+// This file is copied/modified from kaldi/src/feat/online-feature.h
+#ifndef KALDI_NATIVE_FBANK_CSRC_ONLINE_FEATURE_H_
+#define KALDI_NATIVE_FBANK_CSRC_ONLINE_FEATURE_H_
+
+#include <cstdint>
+#include <deque>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/whisper-feature.h"
+
+namespace knf {
+
+/// This class serves as a storage for feature vectors with an option to limit
+/// the memory usage by removing old elements. The deleted frames indices are
+/// "remembered" so that regardless of the MAX_ITEMS setting, the user always
+/// provides the indices as if no deletion was being performed.
+/// This is useful when processing very long recordings which would otherwise
+/// cause the memory to eventually blow up when the features are not being
+/// removed.
+class RecyclingVector {
+ public:
+  /// By default it does not remove any elements.
+  explicit RecyclingVector(int32_t items_to_hold = -1);
+
+  ~RecyclingVector() = default;
+  RecyclingVector(const RecyclingVector &) = delete;
+  RecyclingVector &operator=(const RecyclingVector &) = delete;
+
+  // The pointer is owned by RecyclingVector
+  // Users should not free it
+  const float *At(int32_t index) const;
+
+  void PushBack(std::vector<float> item);
+
+  /// This method returns the size as if no "recycling" had happened,
+  /// i.e. equivalent to the number of times the PushBack method has been
+  /// called.
+  int32_t Size() const;
+
+  // discard the first n frames
+  void Pop(int32_t n);
+
+ private:
+  std::deque<std::vector<float>> items_;
+  int32_t items_to_hold_;
+  int32_t first_available_index_;
+};
+
+/// This is a templated class for online feature extraction;
+/// it's templated on a class like MfccComputer or PlpComputer
+/// that does the basic feature extraction.
+template <class C>
+class OnlineGenericBaseFeature {
+ public:
+  // Constructor from options class
+  explicit OnlineGenericBaseFeature(const typename C::Options &opts);
+
+  int32_t Dim() const { return computer_.Dim(); }
+
+  float FrameShiftInSeconds() const {
+    return computer_.GetFrameOptions().frame_shift_ms / 1000.0f;
+  }
+
+  int32_t NumFramesReady() const { return features_.Size(); }
+
+  // Note: IsLastFrame() will only ever return true if you have called
+  // InputFinished() (and this frame is the last frame).
+  bool IsLastFrame(int32_t frame) const {
+    return input_finished_ && frame == NumFramesReady() - 1;
+  }
+
+  const float *GetFrame(int32_t frame) const { return features_.At(frame); }
+
+  // This would be called from the application, when you get
+  // more wave data.  Note: the sampling_rate is only provided so
+  // the code can assert that it matches the sampling rate
+  // expected in the options.
+  //
+  // @param sampling_rate The sampling_rate of the input waveform
+  // @param waveform Pointer to a 1-D array of size n
+  // @param n Number of entries in waveform
+  void AcceptWaveform(float sampling_rate, const float *waveform, int32_t n);
+
+  // InputFinished() tells the class you won't be providing any
+  // more waveform.  This will help flush out the last frame or two
+  // of features, in the case where snip-edges == false; it also
+  // affects the return value of IsLastFrame().
+  void InputFinished();
+
+  // discard the first n frames
+  void Pop(int32_t n) { features_.Pop(n); }
+
+ private:
+  // This function computes any additional feature frames that it is possible to
+  // compute from 'waveform_remainder_', which at this point may contain more
+  // than just a remainder-sized quantity (because AcceptWaveform() appends to
+  // waveform_remainder_ before calling this function).  It adds these feature
+  // frames to features_, and shifts off any now-unneeded samples of input from
+  // waveform_remainder_ while incrementing waveform_offset_ by the same amount.
+  void ComputeFeatures();
+
+  C computer_;  // class that does the MFCC or PLP or filterbank computation
+
+  FeatureWindowFunction window_function_;
+
+  // features_ is the Mfcc or Plp or Fbank features that we have already
+  // computed.
+
+  RecyclingVector features_;
+
+  // True if the user has called "InputFinished()"
+  bool input_finished_;
+
+  // waveform_offset_ is the number of samples of waveform that we have
+  // already discarded, i.e. that were prior to 'waveform_remainder_'.
+  int64_t waveform_offset_;
+
+  // waveform_remainder_ is a short piece of waveform that we may need to keep
+  // after extracting all the whole frames we can (whatever length of feature
+  // will be required for the next phase of computation).
+  // It is a 1-D tensor
+  std::vector<float> waveform_remainder_;
+};
+
+using OnlineFbank = OnlineGenericBaseFeature<FbankComputer>;
+using OnlineMfcc = OnlineGenericBaseFeature<MfccComputer>;
+using OnlineWhisperFbank = OnlineGenericBaseFeature<WhisperFeatureComputer>;
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_CSRC_ONLINE_FEATURE_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/rfft.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/rfft.cc
new file mode 100644 (file)
index 0000000..619a607
--- /dev/null
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kaldi-native-fbank/csrc/rfft.h"
+
+#include <algorithm>
+#include <cmath>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/log.h"
+
+namespace knf {
+
+// see fftsg.cc
+void rdft(int n, int isgn, double *a, int *ip, double *w);
+
+class Rfft::RfftImpl {
+ public:
+  explicit RfftImpl(int32_t n) : n_(n), ip_(2 + std::sqrt(n / 2)), w_(n / 2) {
+    KNF_CHECK_EQ(n & (n - 1), 0);
+  }
+
+  void Compute(float *in_out) {
+    std::vector<double> d(in_out, in_out + n_);
+
+    Compute(d.data());
+
+    std::copy(d.begin(), d.end(), in_out);
+  }
+
+  void Compute(double *in_out) {
+    // 1 means forward fft
+    rdft(n_, 1, in_out, ip_.data(), w_.data());
+  }
+
+ private:
+  int32_t n_;
+  std::vector<int32_t> ip_;
+  std::vector<double> w_;
+};
+
+Rfft::Rfft(int32_t n) : impl_(std::make_unique<RfftImpl>(n)) {}
+
+Rfft::~Rfft() = default;
+
+void Rfft::Compute(float *in_out) { impl_->Compute(in_out); }
+void Rfft::Compute(double *in_out) { impl_->Compute(in_out); }
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/rfft.h b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/rfft.h
new file mode 100644 (file)
index 0000000..d4652f8
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KALDI_NATIVE_FBANK_CSRC_RFFT_H_
+#define KALDI_NATIVE_FBANK_CSRC_RFFT_H_
+
+#include <memory>
+#include <cstdint>
+
+namespace knf {
+
+// n-point Real discrete Fourier transform
+// where n is a power of 2. n >= 2
+//
+//  R[k] = sum_j=0^n-1 in[j]*cos(2*pi*j*k/n), 0<=k<=n/2
+//  I[k] = sum_j=0^n-1 in[j]*sin(2*pi*j*k/n), 0<k<n/2
+class Rfft {
+ public:
+  // @param n Number of fft bins. it should be a power of 2.
+  explicit Rfft(int32_t n);
+  ~Rfft();
+
+  /** @param in_out A 1-D array of size n.
+   *             On return:
+   *               in_out[0] = R[0]
+   *               in_out[1] = R[n/2]
+   *               for 1 < k < n/2,
+   *                 in_out[2*k] = R[k]
+   *                 in_out[2*k+1] = I[k]
+   *
+   */
+  void Compute(float *in_out);
+  void Compute(double *in_out);
+
+ private:
+  class RfftImpl;
+
+  std::unique_ptr<RfftImpl> impl_;
+};
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_CSRC_RFFT_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-log.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-log.cc
new file mode 100644 (file)
index 0000000..1e6ef73
--- /dev/null
@@ -0,0 +1,71 @@
+/**
+ * Copyright      2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if KNF_ENABLE_CHECK
+#include "gtest/gtest.h"
+#include "kaldi-native-fbank/csrc/log.h"
+
+namespace knf {
+
+TEST(Log, TestLog) {
+  KNF_LOG(TRACE) << "this is a trace message";
+  KNF_LOG(DEBUG) << "this is a debug message";
+  KNF_LOG(INFO) << "this is an info message";
+  KNF_LOG(WARNING) << "this is a warning message";
+  KNF_LOG(ERROR) << "this is an error message";
+
+  ASSERT_THROW(KNF_LOG(FATAL) << "This will crash the program",
+               std::runtime_error);
+
+  // For debug build
+
+  KNF_DLOG(TRACE) << "this is a trace message for debug build";
+  KNF_DLOG(DEBUG) << "this is a trace message for debug build";
+  KNF_DLOG(INFO) << "this is a trace message for debug build";
+  KNF_DLOG(ERROR) << "this is an error message for debug build";
+  KNF_DLOG(WARNING) << "this is a trace message for debug build";
+
+#if !defined(NDEBUG)
+  ASSERT_THROW(KNF_DLOG(FATAL) << "this is a trace message for debug build",
+               std::runtime_error);
+#endif
+}
+
+TEST(Log, TestCheck) {
+  KNF_CHECK_EQ(1, 1) << "ok";
+  KNF_CHECK_LE(1, 3) << "ok";
+  KNF_CHECK_LT(1, 2) << "ok";
+  KNF_CHECK_GT(2, 1) << "ok";
+  KNF_CHECK_GE(2, 1) << "ok";
+
+  ASSERT_THROW(KNF_CHECK_EQ(2, 1) << "bad things happened", std::runtime_error);
+
+  // for debug build
+  KNF_DCHECK_EQ(1, 1) << "ok";
+  KNF_DCHECK_LE(1, 3) << "ok";
+  KNF_DCHECK_LT(1, 2) << "ok";
+  KNF_DCHECK_GT(2, 1) << "ok";
+  KNF_DCHECK_GE(2, 1) << "ok";
+
+#if !defined(NDEBUG)
+  ASSERT_THROW(KNF_CHECK_EQ(2, 1) << "bad things happened", std::runtime_error);
+#endif
+}
+
+}  // namespace knf
+#endif
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-online-fbank.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-online-fbank.cc
new file mode 100644 (file)
index 0000000..c2d437a
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+#include <cstdint>
+#include <sstream>
+#include <string>
+
+#include "kaldi-native-fbank/csrc/online-feature.h"
+#include "kaldi-native-fbank/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+
+int main() {
+  knf::FbankOptions opts;
+  opts.frame_opts.dither = 0;
+  opts.mel_opts.num_bins = 10;
+
+  knf::OnlineFbank fbank(opts);
+  for (int32_t i = 0; i < 1600; ++i) {
+    float s = (i * i - i / 2) / 32767.;
+    fbank.AcceptWaveform(16000, &s, 1);
+  }
+
+  std::ostringstream os;
+
+  int32_t n = fbank.NumFramesReady();
+  for (int32_t i = 0; i != n; ++i) {
+    const float *frame = fbank.GetFrame(i);
+    for (int32_t k = 0; k != opts.mel_opts.num_bins; ++k) {
+      os << frame[k] << ", ";
+    }
+    os << "\n";
+  }
+
+  std::cout << os.str() << "\n";
+
+  return 0;
+}
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-online-feature.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-online-feature.cc
new file mode 100644 (file)
index 0000000..bfbe621
--- /dev/null
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gtest/gtest.h"
+#include "kaldi-native-fbank/csrc/online-feature.h"
+namespace knf {
+
+TEST(RecyclingVector, TestUnlimited) {
+  RecyclingVector v(-1);
+  constexpr int32_t N = 100;
+  for (int32_t i = 0; i != N; ++i) {
+    std::unique_ptr<float[]> p(new float[3]{i, i + 1, i + 2});
+    v.PushBack(std::move(p));
+  }
+  ASSERT_EQ(v.Size(), N);
+
+  for (int32_t i = 0; i != N; ++i) {
+    const float *t = v.At(i);
+    for (int32_t k = 0; k != 3; ++k) {
+      EXPECT_EQ(t[k], (i + k));
+    }
+  }
+}
+
+TEST(RecyclingVector, Testlimited) {
+  constexpr int32_t K = 3;
+  constexpr int32_t N = 10;
+  RecyclingVector v(K);
+  for (int32_t i = 0; i != N; ++i) {
+    std::unique_ptr<float[]> p(new float[3]{i, i + 1, i + 2});
+    v.PushBack(std::move(p));
+  }
+
+  ASSERT_EQ(v.Size(), N);
+
+  for (int32_t i = N - K; i != N; ++i) {
+    const float *t = v.At(i);
+
+    for (int32_t k = 0; k != 3; ++k) {
+      EXPECT_EQ(t[k], (i + k));
+    }
+  }
+}
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-rfft.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/test-rfft.cc
new file mode 100644 (file)
index 0000000..e93adac
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * Copyright      2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdint>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "kaldi-native-fbank/csrc/rfft.h"
+
+namespace knf {
+
+#if 0
+>>> import torch
+>>> a = torch.tensor([1., -1, 3, 8, 20, 6, 0, 2])
+>>> torch.fft.rfft(a)
+tensor([ 39.0000+0.0000j, -28.1924-2.2929j,  18.0000+5.0000j,  -9.8076+3.7071j,
+          9.0000+0.0000j])
+#endif
+
+TEST(Rfft, TestRfft) {
+  knf::Rfft fft(8);
+  for (int32_t i = 0; i != 10; ++i) {
+    std::vector<float> d = {1, -1, 3, 8, 20, 6, 0, 2};
+    fft.Compute(d.data());
+
+    EXPECT_EQ(d[0], 39);
+    EXPECT_EQ(d[1], 9);
+
+    EXPECT_NEAR(d[2], -28.1924, 1e-3);
+    EXPECT_NEAR(-d[3], -2.2929, 1e-3);
+
+    EXPECT_NEAR(d[4], 18, 1e-3);
+    EXPECT_NEAR(-d[5], 5, 1e-3);
+
+    EXPECT_NEAR(d[6], -9.8076, 1e-3);
+    EXPECT_NEAR(-d[7], 3.7071, 1e-3);
+  }
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/whisper-feature.cc b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/whisper-feature.cc
new file mode 100644 (file)
index 0000000..1b69984
--- /dev/null
@@ -0,0 +1,169 @@
+/**
+ * Copyright (c)  2023  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kaldi-native-fbank/csrc/whisper-feature.h"
+
+#include <cmath>
+#include <string>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/log.h"
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+
+#ifndef M_2PI
+#define M_2PI 6.283185307179586476925286766559005
+#endif
+
+namespace knf {
+
+std::string WhisperFeatureOptions::ToString() const {
+  std::ostringstream os;
+  os << "WhisperFeatureOptions(";
+  os << "frame_opts=" << frame_opts.ToString() << ", ";
+  os << "dim=" << dim << ")";
+  return os.str();
+}
+
+static void dft(const std::vector<float> &in, std::vector<float> *out) {
+  // this function is modified from
+  // https://github.com/ggerganov/whisper.cpp/blob/master/whisper.cpp#L2353
+  int32_t N = in.size();
+
+  out->resize(N * 2);
+
+  auto M_2PI_over_N = M_2PI / N;
+  for (int32_t k = 0; k < N; ++k) {
+    float re = 0;
+    float im = 0;
+
+    for (int32_t n = 0; n < N; ++n) {
+      float angle = M_2PI_over_N * k * n;
+      re += in[n] * cos(angle);
+      im -= in[n] * sin(angle);
+    }
+
+    (*out)[k * 2 + 0] = re;
+    (*out)[k * 2 + 1] = im;
+  }
+}
+
+// Cooley-Tukey FFT
+// poor man's implementation - use something better
+// input is real-valued
+// output is complex-valued
+static void fft(const std::vector<float> &in, std::vector<float> *out) {
+  // this function is copied from
+  // https://github.com/ggerganov/whisper.cpp/blob/master/whisper.cpp#L2373C1-L2429C1
+
+  int32_t N = in.size();
+  out->resize(N * 2);
+
+  if (N == 1) {
+    (*out)[0] = in[0];
+    (*out)[1] = 0;
+    return;
+  }
+
+  if (N % 2 == 1) {
+    dft(in, out);
+    return;
+  }
+
+  std::vector<float> even;
+  std::vector<float> odd;
+
+  even.reserve(N / 2);
+  odd.reserve(N / 2);
+
+  for (int32_t i = 0; i != N; ++i) {
+    if (i % 2 == 0) {
+      even.push_back(in[i]);
+    } else {
+      odd.push_back(in[i]);
+    }
+  }
+
+  std::vector<float> even_fft;
+  std::vector<float> odd_fft;
+
+  fft(even, &even_fft);
+  fft(odd, &odd_fft);
+
+  for (int32_t k = 0; k < N / 2; ++k) {
+    float theta = M_2PI * k / N;
+
+    float re = cos(theta);
+    float im = -sin(theta);
+
+    float re_odd = odd_fft[2 * k + 0];
+    float im_odd = odd_fft[2 * k + 1];
+
+    (*out)[2 * k + 0] = even_fft[2 * k + 0] + re * re_odd - im * im_odd;
+    (*out)[2 * k + 1] = even_fft[2 * k + 1] + re * im_odd + im * re_odd;
+
+    (*out)[2 * (k + N / 2) + 0] =
+        even_fft[2 * k + 0] - re * re_odd + im * im_odd;
+    (*out)[2 * (k + N / 2) + 1] =
+        even_fft[2 * k + 1] - re * im_odd - im * re_odd;
+  }
+}
+
+WhisperFeatureComputer::WhisperFeatureComputer(
+    const WhisperFeatureOptions &opts /*= {}*/)
+    : opts_(opts) {
+  opts_.frame_opts.samp_freq = 16000;
+  opts_.frame_opts.frame_shift_ms = 10;
+  opts_.frame_opts.frame_length_ms = 25;
+  opts_.frame_opts.dither = 0;
+  opts_.frame_opts.preemph_coeff = 0;
+  opts_.frame_opts.remove_dc_offset = false;
+  opts_.frame_opts.window_type = "hann";
+  opts_.frame_opts.round_to_power_of_two = false;
+  opts_.frame_opts.snip_edges = false;
+
+  MelBanksOptions mel_opts;
+  mel_opts.num_bins = opts_.dim;
+  mel_opts.low_freq = 0;
+  mel_opts.is_librosa = true;
+
+  mel_banks_ = std::make_unique<MelBanks>(mel_opts, opts_.frame_opts, 1.0f);
+}
+
+void WhisperFeatureComputer::Compute(float /*signal_raw_log_energy*/,
+                                     float /*vtln_warp*/,
+                                     std::vector<float> *signal_frame,
+                                     float *feature) {
+  KNF_CHECK_EQ(signal_frame->size(), frame_opts_.PaddedWindowSize());
+  // we have already applied window function to signal_frame before
+  // calling this method
+  std::vector<float> fft_out;
+  fft(*signal_frame, &fft_out);
+
+  int32_t num_fft = signal_frame->size();
+  std::vector<float> power(num_fft / 2 + 1);
+  for (int32_t i = 0; i <= num_fft / 2; ++i) {
+    float re = fft_out[2 * i + 0];
+    float im = fft_out[2 * i + 1];
+    power[i] = re * re + im * im;
+  }
+
+  // feature is pre-allocated by the user
+  mel_banks_->Compute(power.data(), feature);
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/csrc/whisper-feature.h b/cli/fbank/sys/knf/kaldi-native-fbank/csrc/whisper-feature.h
new file mode 100644 (file)
index 0000000..8ada4bd
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c)  2023  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KALDI_NATIVE_FBANK_CSRC_WHISPER_FEATURE_H_
+#define KALDI_NATIVE_FBANK_CSRC_WHISPER_FEATURE_H_
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+
+namespace knf {
+
+struct WhisperFeatureOptions {
+  WhisperFeatureOptions(const FrameExtractionOptions &frame_opts = {},
+                        int32_t dim = 80)
+      : frame_opts(frame_opts), dim(dim) {}
+
+  FrameExtractionOptions frame_opts;
+  int32_t dim = 80;
+
+  std::string ToString() const;
+};
+
+class WhisperFeatureComputer {
+ public:
+  // note: opts.frame_opts is ignored and we reset it inside
+  explicit WhisperFeatureComputer(const WhisperFeatureOptions &opts = {});
+
+  int32_t Dim() const { return opts_.dim; }
+
+  const FrameExtractionOptions &GetFrameOptions() const {
+    return opts_.frame_opts;
+  }
+
+  void Compute(float /*signal_raw_log_energy*/, float /*vtln_warp*/,
+               std::vector<float> *signal_frame, float *feature);
+
+  // if true, compute log_energy_pre_window but after dithering and dc removal
+  bool NeedRawLogEnergy() const { return false; }
+
+  using Options = WhisperFeatureOptions;
+
+ private:
+  std::unique_ptr<MelBanks> mel_banks_;
+  WhisperFeatureOptions opts_;
+};
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_CSRC_WHISPER_FEATURE_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/CMakeLists.txt b/cli/fbank/sys/knf/kaldi-native-fbank/python/CMakeLists.txt
new file mode 100644 (file)
index 0000000..60d6382
--- /dev/null
@@ -0,0 +1,2 @@
+add_subdirectory(csrc)
+add_subdirectory(tests)
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/CMakeLists.txt b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/CMakeLists.txt
new file mode 100644 (file)
index 0000000..0366b08
--- /dev/null
@@ -0,0 +1,34 @@
+pybind11_add_module(_kaldi_native_fbank
+  feature-fbank.cc
+  feature-mfcc.cc
+  feature-window.cc
+  kaldi-native-fbank.cc
+  mel-computations.cc
+  online-feature.cc
+  rfft.cc
+  utils.cc
+)
+
+if(APPLE)
+  execute_process(
+    COMMAND "${PYTHON_EXECUTABLE}" -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    OUTPUT_VARIABLE PYTHON_SITE_PACKAGE_DIR
+  )
+  message(STATUS "PYTHON_SITE_PACKAGE_DIR: ${PYTHON_SITE_PACKAGE_DIR}")
+  if(PYTHON_SITE_PACKAGE_DIR STREQUAL "")
+    message(WARNING "PYTHON_SITE_PACKAGE_DIR is empty!")
+  else()
+    target_link_libraries(_kaldi_native_fbank PRIVATE "-Wl,-rpath,${PYTHON_SITE_PACKAGE_DIR}")
+  endif()
+endif()
+
+if(NOT WIN32)
+  target_link_libraries(_kaldi_native_fbank PRIVATE "-Wl,-rpath,${kaldi_native_fbank_rpath_origin}/kaldi_native_fbank/lib")
+endif()
+
+target_link_libraries(_kaldi_native_fbank PRIVATE kaldi-native-fbank-core)
+
+install(TARGETS _kaldi_native_fbank
+  DESTINATION ../
+)
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-fbank.cc b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-fbank.cc
new file mode 100644 (file)
index 0000000..870dc86
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kaldi-native-fbank/python/csrc/feature-fbank.h"
+
+#include <string>
+
+#include "kaldi-native-fbank/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/python/csrc/utils.h"
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+
+namespace knf {
+
+static void PybindFbankOptions(py::module &m) {  // NOLINT
+  using PyClass = FbankOptions;
+  py::class_<PyClass>(m, "FbankOptions")
+      .def(py::init<>())
+      .def_readwrite("frame_opts", &PyClass::frame_opts)
+      .def_readwrite("mel_opts", &PyClass::mel_opts)
+      .def_readwrite("use_energy", &PyClass::use_energy)
+      .def_readwrite("energy_floor", &PyClass::energy_floor)
+      .def_readwrite("raw_energy", &PyClass::raw_energy)
+      .def_readwrite("htk_compat", &PyClass::htk_compat)
+      .def_readwrite("use_log_fbank", &PyClass::use_log_fbank)
+      .def_readwrite("use_power", &PyClass::use_power)
+      .def("__str__",
+           [](const PyClass &self) -> std::string { return self.ToString(); })
+      .def("as_dict",
+           [](const PyClass &self) -> py::dict { return AsDict(self); })
+      .def_static(
+          "from_dict",
+          [](py::dict dict) -> PyClass { return FbankOptionsFromDict(dict); })
+      .def(py::pickle(
+          [](const PyClass &self) -> py::dict { return AsDict(self); },
+          [](py::dict dict) -> PyClass { return FbankOptionsFromDict(dict); }));
+}
+
+void PybindFeatureFbank(py::module &m) {  // NOLINT
+  PybindFbankOptions(m);
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-fbank.h b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-fbank.h
new file mode 100644 (file)
index 0000000..6490c22
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_FBANK_H_
+#define KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_FBANK_H_
+
+#include "kaldi-native-fbank/python/csrc/kaldi-native-fbank.h"
+
+namespace knf {
+
+void PybindFeatureFbank(py::module &m);  // NOLINT
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_FBANK_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-mfcc.cc b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-mfcc.cc
new file mode 100644 (file)
index 0000000..d910016
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c)  2024  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
+
+#include <string>
+
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+#include "kaldi-native-fbank/python/csrc/feature-mfcc.h"
+#include "kaldi-native-fbank/python/csrc/utils.h"
+
+namespace knf {
+
+static void PybindMfccOptions(py::module &m) {  // NOLINT
+  using PyClass = MfccOptions;
+  py::class_<PyClass>(m, "MfccOptions")
+      .def(py::init<>())
+      .def_readwrite("frame_opts", &PyClass::frame_opts)
+      .def_readwrite("mel_opts", &PyClass::mel_opts)
+      .def_readwrite("num_ceps", &PyClass::num_ceps)
+      .def_readwrite("use_energy", &PyClass::use_energy)
+      .def_readwrite("energy_floor", &PyClass::energy_floor)
+      .def_readwrite("raw_energy", &PyClass::raw_energy)
+      .def_readwrite("cepstral_lifter", &PyClass::cepstral_lifter)
+      .def_readwrite("htk_compat", &PyClass::htk_compat)
+      .def("__str__",
+           [](const PyClass &self) -> std::string { return self.ToString(); })
+      .def("as_dict",
+           [](const PyClass &self) -> py::dict { return AsDict(self); })
+      .def_static(
+          "from_dict",
+          [](py::dict dict) -> PyClass { return MfccOptionsFromDict(dict); })
+      .def(py::pickle(
+          [](const PyClass &self) -> py::dict { return AsDict(self); },
+          [](py::dict dict) -> PyClass { return MfccOptionsFromDict(dict); }));
+}
+
+void PybindFeatureMfcc(py::module &m) {  // NOLINT
+  PybindMfccOptions(m);
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-mfcc.h b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-mfcc.h
new file mode 100644 (file)
index 0000000..1a44578
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c)  2024  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_MFCC_H_
+#define KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_MFCC_H_
+
+#include "kaldi-native-fbank/python/csrc/kaldi-native-fbank.h"
+
+namespace knf {
+
+void PybindFeatureMfcc(py::module &m);  // NOLINT
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_MFCC_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-window.cc b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-window.cc
new file mode 100644 (file)
index 0000000..196e832
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kaldi-native-fbank/python/csrc/feature-window.h"
+
+#include <string>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/python/csrc/utils.h"
+
+namespace knf {
+
+static void PybindFrameExtractionOptions(py::module &m) {  // NOLINT
+  using PyClass = FrameExtractionOptions;
+  py::class_<PyClass>(m, "FrameExtractionOptions")
+      .def(py::init<>())
+      .def_readwrite("samp_freq", &PyClass::samp_freq)
+      .def_readwrite("frame_shift_ms", &PyClass::frame_shift_ms)
+      .def_readwrite("frame_length_ms", &PyClass::frame_length_ms)
+      .def_readwrite("dither", &PyClass::dither)
+      .def_readwrite("preemph_coeff", &PyClass::preemph_coeff)
+      .def_readwrite("remove_dc_offset", &PyClass::remove_dc_offset)
+      .def_readwrite("window_type", &PyClass::window_type)
+      .def_readwrite("round_to_power_of_two", &PyClass::round_to_power_of_two)
+      .def_readwrite("blackman_coeff", &PyClass::blackman_coeff)
+      .def_readwrite("snip_edges", &PyClass::snip_edges)
+      .def("as_dict",
+           [](const PyClass &self) -> py::dict { return AsDict(self); })
+      .def_static("from_dict",
+                  [](py::dict dict) -> PyClass {
+                    return FrameExtractionOptionsFromDict(dict);
+                  })
+#if 0
+      .def_readwrite("allow_downsample",
+                     &PyClass::allow_downsample)
+      .def_readwrite("allow_upsample", &PyClass::allow_upsample)
+#endif
+      .def("__str__",
+           [](const PyClass &self) -> std::string { return self.ToString(); })
+      .def(py::pickle(
+          [](const PyClass &self) -> py::dict { return AsDict(self); },
+          [](py::dict dict) -> PyClass {
+            return FrameExtractionOptionsFromDict(dict);
+          }));
+}
+static void PybindFeatureWindowFunction(py::module *m) {
+  using PyClass = FeatureWindowFunction;
+  py::class_<PyClass>(*m, "FeatureWindowFunction")
+      .def(py::init<const FrameExtractionOptions &>(), py::arg("opts"))
+      .def("apply",
+           [](const PyClass &self,
+              std::vector<float> &wave) -> std::vector<float> {
+             self.Apply(wave.data());
+             return wave;
+           })
+      .def_property_readonly("window", &PyClass::GetWindow);
+}
+
+void PybindFeatureWindow(py::module &m) {  // NOLINT
+  PybindFrameExtractionOptions(m);
+  PybindFeatureWindowFunction(&m);
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-window.h b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/feature-window.h
new file mode 100644 (file)
index 0000000..aba5b9e
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_WINDOW_H_
+#define KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_WINDOW_H_
+
+#include "kaldi-native-fbank/python/csrc/kaldi-native-fbank.h"
+
+namespace knf {
+
+void PybindFeatureWindow(py::module &m);  // NOLINT
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_PYTHON_CSRC_FEATURE_WINDOW_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/kaldi-native-fbank.cc b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/kaldi-native-fbank.cc
new file mode 100644 (file)
index 0000000..be12a47
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kaldi-native-fbank/python/csrc/kaldi-native-fbank.h"
+
+#include "kaldi-native-fbank/python/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/python/csrc/feature-mfcc.h"
+#include "kaldi-native-fbank/python/csrc/feature-window.h"
+#include "kaldi-native-fbank/python/csrc/mel-computations.h"
+#include "kaldi-native-fbank/python/csrc/online-feature.h"
+#include "kaldi-native-fbank/python/csrc/rfft.h"
+
+namespace knf {
+
+PYBIND11_MODULE(_kaldi_native_fbank, m) {
+  m.doc() = "Python wrapper for kaldi native fbank";
+  PybindFeatureWindow(m);
+  PybindMelComputations(m);
+  PybindFeatureFbank(m);
+  PybindFeatureMfcc(m);
+  PybindRfft(m);
+
+  PybindOnlineFeature(m);
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/kaldi-native-fbank.h b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/kaldi-native-fbank.h
new file mode 100644 (file)
index 0000000..756f4ce
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KALDI_NATIVE_FBANK_PYTHON_CSRC_KALDI_NATIVE_FBANK_H_
+#define KALDI_NATIVE_FBANK_PYTHON_CSRC_KALDI_NATIVE_FBANK_H_
+
+#include "pybind11/numpy.h"
+#include "pybind11/pybind11.h"
+#include "pybind11/stl.h"
+namespace py = pybind11;
+
+#endif  // KALDI_NATIVE_FBANK_PYTHON_CSRC_KALDI_NATIVE_FBANK_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/mel-computations.cc b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/mel-computations.cc
new file mode 100644 (file)
index 0000000..305dcca
--- /dev/null
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kaldi-native-fbank/python/csrc/mel-computations.h"
+
+#include <string>
+
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+#include "kaldi-native-fbank/python/csrc/utils.h"
+
+namespace knf {
+
+static void PybindMelBanksOptions(py::module &m) {  // NOLINT
+  using PyClass = MelBanksOptions;
+  py::class_<PyClass>(m, "MelBanksOptions")
+      .def(py::init<>())
+      .def_readwrite("num_bins", &PyClass::num_bins)
+      .def_readwrite("low_freq", &PyClass::low_freq)
+      .def_readwrite("high_freq", &PyClass::high_freq)
+      .def_readwrite("vtln_low", &PyClass::vtln_low)
+      .def_readwrite("vtln_high", &PyClass::vtln_high)
+      .def_readwrite("debug_mel", &PyClass::debug_mel)
+      .def_readwrite("htk_mode", &PyClass::htk_mode)
+      .def_readwrite("is_librosa", &PyClass::is_librosa)
+      .def_readwrite("norm", &PyClass::norm)
+      .def("__str__",
+           [](const PyClass &self) -> std::string { return self.ToString(); })
+      .def("as_dict",
+           [](const PyClass &self) -> py::dict { return AsDict(self); })
+      .def_static("from_dict",
+                  [](py::dict dict) -> PyClass {
+                    return MelBanksOptionsFromDict(dict);
+                  })
+      .def(py::pickle(
+          [](const PyClass &self) -> py::dict { return AsDict(self); },
+          [](py::dict dict) -> PyClass {
+            return MelBanksOptionsFromDict(dict);
+          }));
+}
+
+void PybindMelComputations(py::module &m) {  // NOLINT
+  PybindMelBanksOptions(m);
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/mel-computations.h b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/mel-computations.h
new file mode 100644 (file)
index 0000000..2ca9ac7
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KALDI_NATIVE_FBANK_PYTHON_CSRC_MEL_COMPUTATIONS_H_
+#define KALDI_NATIVE_FBANK_PYTHON_CSRC_MEL_COMPUTATIONS_H_
+
+#include "kaldi-native-fbank/python/csrc/kaldi-native-fbank.h"
+
+namespace knf {
+
+void PybindMelComputations(py::module &m);  // NOLINT
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_PYTHON_CSRC_MEL_COMPUTATIONS_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/online-feature.cc b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/online-feature.cc
new file mode 100644 (file)
index 0000000..b650c13
--- /dev/null
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kaldi-native-fbank/python/csrc/online-feature.h"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "kaldi-native-fbank/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
+#include "kaldi-native-fbank/csrc/online-feature.h"
+#include "kaldi-native-fbank/csrc/whisper-feature.h"
+#include "kaldi-native-fbank/python/csrc/utils.h"
+
+namespace pybind11 {
+class gil_scoped_release;
+}  // namespace pybind11
+
+namespace knf {
+
+static void PybindWhisperFeatureOptions(py::module &m) {  // NOLINT
+  using PyClass = WhisperFeatureOptions;
+  py::class_<PyClass>(m, "WhisperFeatureOptions")
+      .def(py::init<>())
+      .def_readwrite("frame_opts", &PyClass::frame_opts)
+      .def_readwrite("dim", &PyClass::dim)
+      .def("__str__",
+           [](const PyClass &self) -> std::string { return self.ToString(); })
+      .def("as_dict",
+           [](const PyClass &self) -> py::dict { return AsDict(self); })
+      .def_static("from_dict",
+                  [](py::dict dict) -> PyClass {
+                    return WhisperFeatureOptionsFromDict(dict);
+                  })
+      .def(py::pickle(
+          [](const PyClass &self) -> py::dict { return AsDict(self); },
+          [](py::dict dict) -> PyClass {
+            return WhisperFeatureOptionsFromDict(dict);
+          }));
+}
+
+template <typename C>
+void PybindOnlineFeatureTpl(py::module &m,  // NOLINT
+                            const std::string &class_name,
+                            const std::string &class_help_doc = "") {
+  using PyClass = OnlineGenericBaseFeature<C>;
+  using Options = typename C::Options;
+  py::class_<PyClass>(m, class_name.c_str(), class_help_doc.c_str())
+      .def(py::init<const Options &>(), py::arg("opts"))
+      .def_property_readonly("dim", &PyClass::Dim)
+      .def_property_readonly("frame_shift_in_seconds",
+                             &PyClass::FrameShiftInSeconds)
+      .def_property_readonly("num_frames_ready", &PyClass::NumFramesReady)
+      .def("is_last_frame", &PyClass::IsLastFrame, py::arg("frame"))
+      .def(
+          "get_frame",
+          [](py::object obj, int32_t frame) {
+            auto *self = obj.cast<PyClass *>();
+            const float *f = self->GetFrame(frame);
+            return py::array_t<float>({self->Dim()},    // shape
+                                      {sizeof(float)},  // stride in bytes
+                                      f,                // ptr
+                                      obj);  // it will increase the reference
+                                             // count of **this** vector
+          },
+          py::arg("frame"))
+      .def(
+          "accept_waveform",
+          [](PyClass &self, float sampling_rate,
+             const std::vector<float> &waveform) {
+            self.AcceptWaveform(sampling_rate, waveform.data(),
+                                waveform.size());
+          },
+          py::arg("sampling_rate"), py::arg("waveform"),
+          py::call_guard<py::gil_scoped_release>())
+      .def("input_finished", &PyClass::InputFinished,
+           py::call_guard<py::gil_scoped_release>())
+      .def("pop", &PyClass::Pop, py::arg("n"),
+           py::call_guard<py::gil_scoped_release>());
+}
+
+void PybindOnlineFeature(py::module &m) {  // NOLINT
+  PybindOnlineFeatureTpl<FbankComputer>(m, "OnlineFbank");
+  PybindOnlineFeatureTpl<MfccComputer>(m, "OnlineMfcc");
+
+  PybindWhisperFeatureOptions(m);
+
+  PybindOnlineFeatureTpl<WhisperFeatureComputer>(m, "OnlineWhisperFbank");
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/online-feature.h b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/online-feature.h
new file mode 100644 (file)
index 0000000..b4a05df
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KALDI_NATIVE_FBANK_PYTHON_CSRC_ONLINE_FEATURE_H_
+#define KALDI_NATIVE_FBANK_PYTHON_CSRC_ONLINE_FEATURE_H_
+
+#include "kaldi-native-fbank/python/csrc/kaldi-native-fbank.h"
+
+namespace knf {
+
+void PybindOnlineFeature(py::module &m);  // NOLINT
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_PYTHON_CSRC_ONLINE_FEATURE_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/rfft.cc b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/rfft.cc
new file mode 100644 (file)
index 0000000..90d2ea3
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c)  2022-2023  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kaldi-native-fbank/python/csrc/rfft.h"
+
+#include <vector>
+#include <cstdint>
+
+#include "kaldi-native-fbank/csrc/rfft.h"
+
+namespace knf {
+
+void PybindRfft(py::module &m) {  // NOLINT
+  py::class_<Rfft>(m, "Rfft")
+      .def(py::init<int32_t>(), py::arg("n"))
+      .def("compute",
+           [](Rfft &self, std::vector<float> &d) -> std::vector<float> {
+             self.Compute(d.data());
+             return d;
+           });
+}
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/rfft.h b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/rfft.h
new file mode 100644 (file)
index 0000000..49ffa46
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c)  2022-2023  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KALDI_NATIVE_FBANK_PYTHON_CSRC_RFFT_H_
+#define KALDI_NATIVE_FBANK_PYTHON_CSRC_RFFT_H_
+
+#include "kaldi-native-fbank/python/csrc/kaldi-native-fbank.h"
+
+namespace knf {
+
+void PybindRfft(py::module &m);  // NOLINT
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_PYTHON_CSRC_RFFT_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/utils.cc b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/utils.cc
new file mode 100644 (file)
index 0000000..3a5cc40
--- /dev/null
@@ -0,0 +1,199 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "kaldi-native-fbank/python/csrc/utils.h"
+
+#include <string>
+
+#include "kaldi-native-fbank/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/whisper-feature.h"
+
+#define FROM_DICT(type, key)         \
+  if (dict.contains(#key)) {         \
+    opts.key = py::type(dict[#key]); \
+  }
+
+#define AS_DICT(key) dict[#key] = opts.key
+
+namespace knf {
+
+FrameExtractionOptions FrameExtractionOptionsFromDict(py::dict dict) {
+  FrameExtractionOptions opts;
+
+  FROM_DICT(float_, samp_freq);
+  FROM_DICT(float_, frame_shift_ms);
+  FROM_DICT(float_, frame_length_ms);
+  FROM_DICT(float_, dither);
+  FROM_DICT(float_, preemph_coeff);
+  FROM_DICT(bool_, remove_dc_offset);
+  FROM_DICT(str, window_type);
+  FROM_DICT(bool_, round_to_power_of_two);
+  FROM_DICT(float_, blackman_coeff);
+  FROM_DICT(bool_, snip_edges);
+
+  return opts;
+}
+
+py::dict AsDict(const FrameExtractionOptions &opts) {
+  py::dict dict;
+
+  AS_DICT(samp_freq);
+  AS_DICT(frame_shift_ms);
+  AS_DICT(frame_length_ms);
+  AS_DICT(dither);
+  AS_DICT(preemph_coeff);
+  AS_DICT(remove_dc_offset);
+  AS_DICT(window_type);
+  AS_DICT(round_to_power_of_two);
+  AS_DICT(blackman_coeff);
+  AS_DICT(snip_edges);
+
+  return dict;
+}
+
+MelBanksOptions MelBanksOptionsFromDict(py::dict dict) {
+  MelBanksOptions opts;
+
+  FROM_DICT(int_, num_bins);
+  FROM_DICT(float_, low_freq);
+  FROM_DICT(float_, high_freq);
+  FROM_DICT(float_, vtln_low);
+  FROM_DICT(float_, vtln_high);
+  FROM_DICT(bool_, debug_mel);
+  FROM_DICT(bool_, htk_mode);
+  FROM_DICT(bool_, is_librosa);
+  FROM_DICT(str, norm);
+
+  return opts;
+}
+
+py::dict AsDict(const MelBanksOptions &opts) {
+  py::dict dict;
+
+  AS_DICT(num_bins);
+  AS_DICT(low_freq);
+  AS_DICT(high_freq);
+  AS_DICT(vtln_low);
+  AS_DICT(vtln_high);
+  AS_DICT(debug_mel);
+  AS_DICT(htk_mode);
+  AS_DICT(is_librosa);
+  AS_DICT(norm);
+
+  return dict;
+}
+
+FbankOptions FbankOptionsFromDict(py::dict dict) {
+  FbankOptions opts;
+
+  if (dict.contains("frame_opts")) {
+    opts.frame_opts = FrameExtractionOptionsFromDict(dict["frame_opts"]);
+  }
+
+  if (dict.contains("mel_opts")) {
+    opts.mel_opts = MelBanksOptionsFromDict(dict["mel_opts"]);
+  }
+
+  FROM_DICT(bool_, use_energy);
+  FROM_DICT(float_, energy_floor);
+  FROM_DICT(bool_, raw_energy);
+  FROM_DICT(bool_, htk_compat);
+  FROM_DICT(bool_, use_log_fbank);
+  FROM_DICT(bool_, use_power);
+
+  return opts;
+}
+
+py::dict AsDict(const FbankOptions &opts) {
+  py::dict dict;
+
+  dict["frame_opts"] = AsDict(opts.frame_opts);
+  dict["mel_opts"] = AsDict(opts.mel_opts);
+  AS_DICT(use_energy);
+  AS_DICT(energy_floor);
+  AS_DICT(raw_energy);
+  AS_DICT(htk_compat);
+  AS_DICT(use_log_fbank);
+  AS_DICT(use_power);
+
+  return dict;
+}
+
+MfccOptions MfccOptionsFromDict(py::dict dict) {
+  MfccOptions opts;
+
+  if (dict.contains("frame_opts")) {
+    opts.frame_opts = FrameExtractionOptionsFromDict(dict["frame_opts"]);
+  }
+
+  if (dict.contains("mel_opts")) {
+    opts.mel_opts = MelBanksOptionsFromDict(dict["mel_opts"]);
+  }
+
+  FROM_DICT(int_, num_ceps);
+  FROM_DICT(bool_, use_energy);
+  FROM_DICT(float_, energy_floor);
+  FROM_DICT(bool_, raw_energy);
+  FROM_DICT(float_, cepstral_lifter);
+  FROM_DICT(bool_, htk_compat);
+
+  return opts;
+}
+
+py::dict AsDict(const MfccOptions &opts) {
+  py::dict dict;
+
+  dict["frame_opts"] = AsDict(opts.frame_opts);
+  dict["mel_opts"] = AsDict(opts.mel_opts);
+  AS_DICT(num_ceps);
+  AS_DICT(use_energy);
+  AS_DICT(energy_floor);
+  AS_DICT(raw_energy);
+  AS_DICT(cepstral_lifter);
+  AS_DICT(htk_compat);
+
+  return dict;
+}
+
+WhisperFeatureOptions WhisperFeatureOptionsFromDict(py::dict dict) {
+  WhisperFeatureOptions opts;
+
+  if (dict.contains("frame_opts")) {
+    opts.frame_opts = FrameExtractionOptionsFromDict(dict["frame_opts"]);
+  }
+
+  FROM_DICT(int_, dim);
+
+  return opts;
+}
+
+py::dict AsDict(const WhisperFeatureOptions &opts) {
+  py::dict dict;
+
+  dict["frame_opts"] = AsDict(opts.frame_opts);
+  AS_DICT(dim);
+
+  return dict;
+}
+
+#undef FROM_DICT
+#undef AS_DICT
+
+}  // namespace knf
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/utils.h b/cli/fbank/sys/knf/kaldi-native-fbank/python/csrc/utils.h
new file mode 100644 (file)
index 0000000..ab6bee1
--- /dev/null
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+ *
+ * See LICENSE for clarification regarding multiple authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef KALDI_NATIVE_FBANK_PYTHON_CSRC_UTILS_H_
+#define KALDI_NATIVE_FBANK_PYTHON_CSRC_UTILS_H_
+
+#include "kaldi-native-fbank/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/csrc/feature-mfcc.h"
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+#include "kaldi-native-fbank/csrc/whisper-feature.h"
+#include "kaldi-native-fbank/python/csrc/kaldi-native-fbank.h"
+
+/*
+ * This file contains code about `from_dict` and
+ * `as_dict` for various options in kaldi-native-fbank.
+ *
+ * Regarding `from_dict`, users don't need to provide
+ * all the fields in the options. If some fields
+ * are not provided, it just uses the default one.
+ *
+ * If the provided dict in `from_dict` is empty,
+ * all fields use their default values.
+ */
+
+namespace knf {
+
+FrameExtractionOptions FrameExtractionOptionsFromDict(py::dict dict);
+py::dict AsDict(const FrameExtractionOptions &opts);
+
+MelBanksOptions MelBanksOptionsFromDict(py::dict dict);
+py::dict AsDict(const MelBanksOptions &opts);
+
+FbankOptions FbankOptionsFromDict(py::dict dict);
+py::dict AsDict(const FbankOptions &opts);
+
+MfccOptions MfccOptionsFromDict(py::dict dict);
+py::dict AsDict(const MfccOptions &opts);
+
+WhisperFeatureOptions WhisperFeatureOptionsFromDict(py::dict dict);
+py::dict AsDict(const WhisperFeatureOptions &opts);
+
+}  // namespace knf
+
+#endif  // KALDI_NATIVE_FBANK_PYTHON_CSRC_UTILS_H_
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/kaldi_native_fbank/__init__.py b/cli/fbank/sys/knf/kaldi-native-fbank/python/kaldi_native_fbank/__init__.py
new file mode 100644 (file)
index 0000000..d336426
--- /dev/null
@@ -0,0 +1,12 @@
+from _kaldi_native_fbank import (
+    FbankOptions,
+    FeatureWindowFunction,
+    FrameExtractionOptions,
+    MelBanksOptions,
+    MfccOptions,
+    OnlineFbank,
+    OnlineMfcc,
+    OnlineWhisperFbank,
+    Rfft,
+    WhisperFeatureOptions,
+)
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/CMakeLists.txt b/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d2c671c
--- /dev/null
@@ -0,0 +1,36 @@
+function(kaldi_native_fbank_add_py_test source)
+  get_filename_component(name ${source} NAME_WE)
+  set(name "${name}_py")
+
+    message(STATUS "source: ${source}")
+
+  add_test(NAME ${name}
+    COMMAND
+      "${PYTHON_EXECUTABLE}"
+      "${CMAKE_CURRENT_SOURCE_DIR}/${source}"
+  )
+
+  get_filename_component(kaldi_native_fbank_path ${CMAKE_CURRENT_LIST_DIR} DIRECTORY)
+
+  set_property(TEST ${name}
+    PROPERTY ENVIRONMENT "PYTHONPATH=${kaldi_native_fbank_path}:$<TARGET_FILE_DIR:_kaldi_native_fbank>:$ENV{PYTHONPATH}"
+  )
+endfunction()
+
+# please sort the files in alphabetic order
+set(py_test_files
+  test_fbank_options.py
+  test_feature_window_function.py
+  test_frame_extraction_options.py
+  test_mel_bank_options.py
+  test_online_whisper_fbank.py
+  test_online_fbank.py
+  test_online_mfcc.py
+  # test_rfft.py
+)
+
+if(KALDI_NATIVE_FBANK_BUILD_TESTS)
+  foreach(source IN LISTS py_test_files)
+    kaldi_native_fbank_add_py_test(${source})
+  endforeach()
+endif()
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_fbank_options.py b/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_fbank_options.py
new file mode 100755 (executable)
index 0000000..3c291df
--- /dev/null
@@ -0,0 +1,198 @@
+#!/usr/bin/env python3
+#
+# Copyright (c)  2021  Xiaomi Corporation (authors: Fangjun Kuang)
+
+
+import pickle
+
+import kaldi_native_fbank as knf
+
+
+def test_default():
+    opts = knf.FbankOptions()
+    assert opts.frame_opts.samp_freq == 16000
+    assert opts.frame_opts.frame_shift_ms == 10.0
+    assert opts.frame_opts.frame_length_ms == 25.0
+    assert abs(opts.frame_opts.dither - 0.00003) < 1e-6
+    assert abs(opts.frame_opts.preemph_coeff - 0.97) < 1e-6
+    assert opts.frame_opts.remove_dc_offset is True
+    assert opts.frame_opts.window_type == "povey"
+    assert opts.frame_opts.round_to_power_of_two is True
+    assert abs(opts.frame_opts.blackman_coeff - 0.42) < 1e-6
+    assert opts.frame_opts.snip_edges is True
+
+    assert opts.mel_opts.num_bins == 23
+    assert opts.mel_opts.low_freq == 20
+    assert opts.mel_opts.high_freq == 0
+    assert opts.mel_opts.vtln_low == 100
+    assert opts.mel_opts.vtln_high == -500
+    assert opts.mel_opts.debug_mel is False
+    assert opts.mel_opts.htk_mode is False
+
+    assert opts.use_energy is False
+    assert opts.energy_floor == 0.0
+    assert opts.raw_energy is True
+    assert opts.htk_compat is False
+    assert opts.use_log_fbank is True
+    assert opts.use_power is True
+
+
+def test_set_get():
+    opts = knf.FbankOptions()
+    opts.use_energy = True
+    assert opts.use_energy is True
+
+    opts.energy_floor = 1
+    assert opts.energy_floor == 1
+
+    opts.raw_energy = False
+    assert opts.raw_energy is False
+
+    opts.htk_compat = True
+    assert opts.htk_compat is True
+
+    opts.use_log_fbank = False
+    assert opts.use_log_fbank is False
+
+    opts.use_power = False
+    assert opts.use_power is False
+
+
+def test_set_get_frame_opts():
+    opts = knf.FbankOptions()
+
+    opts.frame_opts.samp_freq = 44100
+    assert opts.frame_opts.samp_freq == 44100
+
+    opts.frame_opts.frame_shift_ms = 20.5
+    assert opts.frame_opts.frame_shift_ms == 20.5
+
+    opts.frame_opts.frame_length_ms = 1
+    assert opts.frame_opts.frame_length_ms == 1
+
+    opts.frame_opts.dither = 0.5
+    assert opts.frame_opts.dither == 0.5
+
+    opts.frame_opts.preemph_coeff = 0.25
+    assert opts.frame_opts.preemph_coeff == 0.25
+
+    opts.frame_opts.remove_dc_offset = False
+    assert opts.frame_opts.remove_dc_offset is False
+
+    opts.frame_opts.window_type = "hanning"
+    assert opts.frame_opts.window_type == "hanning"
+
+    opts.frame_opts.round_to_power_of_two = False
+    assert opts.frame_opts.round_to_power_of_two is False
+
+    opts.frame_opts.blackman_coeff = 0.25
+    assert opts.frame_opts.blackman_coeff == 0.25
+
+    opts.frame_opts.snip_edges = False
+    assert opts.frame_opts.snip_edges is False
+
+
+def test_set_get_mel_opts():
+    opts = knf.FbankOptions()
+
+    opts.mel_opts.num_bins = 100
+    assert opts.mel_opts.num_bins == 100
+
+    opts.mel_opts.low_freq = 22
+    assert opts.mel_opts.low_freq == 22
+
+    opts.mel_opts.high_freq = 1
+    assert opts.mel_opts.high_freq == 1
+
+    opts.mel_opts.vtln_low = 101
+    assert opts.mel_opts.vtln_low == 101
+
+    opts.mel_opts.vtln_high = -100
+    assert opts.mel_opts.vtln_high == -100
+
+    opts.mel_opts.debug_mel = True
+    assert opts.mel_opts.debug_mel is True
+
+    opts.mel_opts.htk_mode = True
+    assert opts.mel_opts.htk_mode is True
+
+
+def test_from_empty_dict():
+    opts = knf.FbankOptions.from_dict({})
+    opts2 = knf.FbankOptions()
+
+    assert str(opts) == str(opts2)
+
+
+def test_from_dict_partial():
+    d = {
+        "energy_floor": 10.5,
+        "htk_compat": True,
+        "mel_opts": {"num_bins": 80, "vtln_low": 1},
+        "frame_opts": {"window_type": "hanning"},
+    }
+    opts = knf.FbankOptions.from_dict(d)
+    assert opts.energy_floor == 10.5
+    assert opts.htk_compat is True
+    assert opts.mel_opts.num_bins == 80
+    assert opts.mel_opts.vtln_low == 1
+    assert opts.frame_opts.window_type == "hanning"
+
+    mel_opts = knf.MelBanksOptions.from_dict(d["mel_opts"])
+    assert str(opts.mel_opts) == str(mel_opts)
+
+
+def test_from_dict_full_and_as_dict():
+    opts = knf.FbankOptions()
+    opts.htk_compat = True
+    opts.mel_opts.num_bins = 80
+    opts.frame_opts.samp_freq = 10
+
+    d = opts.as_dict()
+    assert d["htk_compat"] is True
+    assert d["mel_opts"]["num_bins"] == 80
+    assert d["frame_opts"]["samp_freq"] == 10
+
+    mel_opts = knf.MelBanksOptions()
+    mel_opts.num_bins = 80
+    assert d["mel_opts"] == mel_opts.as_dict()
+
+    frame_opts = knf.FrameExtractionOptions()
+    frame_opts.samp_freq = 10
+    assert d["frame_opts"] == frame_opts.as_dict()
+
+    opts2 = knf.FbankOptions.from_dict(d)
+    assert str(opts2) == str(opts)
+
+    d["htk_compat"] = False
+    opts3 = knf.FbankOptions.from_dict(d)
+    assert opts3.htk_compat is False
+
+
+def test_pickle():
+    opts = knf.FbankOptions()
+    opts.use_energy = True
+    opts.use_power = False
+
+    opts.frame_opts.samp_freq = 44100
+    opts.mel_opts.num_bins = 100
+
+    data = pickle.dumps(opts)
+
+    opts2 = pickle.loads(data)
+    assert str(opts) == str(opts2)
+
+
+def main():
+    test_default()
+    test_set_get()
+    test_set_get_frame_opts()
+    test_set_get_mel_opts()
+    test_from_empty_dict()
+    test_from_dict_partial()
+    test_from_dict_full_and_as_dict()
+    test_pickle()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_feature_window_function.py b/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_feature_window_function.py
new file mode 100755 (executable)
index 0000000..f05a822
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+#
+# Copyright (c)  2022-2023  Xiaomi Corporation (authors: Fangjun Kuang)
+
+import kaldi_native_fbank as knf
+import torch
+
+
+def test_hann_window():
+    opts = knf.FrameExtractionOptions()
+    assert opts.samp_freq == 16000
+    assert opts.frame_shift_ms == 10.0
+    assert opts.frame_length_ms == 25.0
+    opts.window_type = "hann"
+
+    n = int((opts.samp_freq * opts.frame_length_ms) / 1000)
+
+    window = knf.FeatureWindowFunction(opts)
+    assert len(window.window) == n
+    w = torch.tensor(window.window)
+
+    t = torch.hann_window(n, periodic=True)
+    assert torch.allclose(w, t, atol=1e-6), (w - t).abs().max()
+
+
+def main():
+    test_hann_window()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_frame_extraction_options.py b/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_frame_extraction_options.py
new file mode 100755 (executable)
index 0000000..f17d63b
--- /dev/null
@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+#
+# Copyright (c)  2022  Xiaomi Corporation (authors: Fangjun Kuang)
+
+import pickle
+
+import kaldi_native_fbank as knf
+
+
+def test_default():
+    opts = knf.FrameExtractionOptions()
+    assert opts.samp_freq == 16000
+    assert opts.frame_shift_ms == 10.0
+    assert opts.frame_length_ms == 25.0
+    assert abs(opts.dither - 0.00003) < 1e-6
+    assert abs(opts.preemph_coeff - 0.97) < 1e-6
+    assert opts.remove_dc_offset is True
+    assert opts.window_type == "povey"
+    assert opts.round_to_power_of_two is True
+    assert abs(opts.blackman_coeff - 0.42) < 1e-6
+    assert opts.snip_edges is True
+
+
+def test_set_get():
+    opts = knf.FrameExtractionOptions()
+    opts.samp_freq = 44100
+    assert opts.samp_freq == 44100
+
+    opts.frame_shift_ms = 20.5
+    assert opts.frame_shift_ms == 20.5
+
+    opts.frame_length_ms = 1
+    assert opts.frame_length_ms == 1
+
+    opts.dither = 0.5
+    assert opts.dither == 0.5
+
+    opts.preemph_coeff = 0.25
+    assert opts.preemph_coeff == 0.25
+
+    opts.remove_dc_offset = False
+    assert opts.remove_dc_offset is False
+
+    opts.window_type = "hanning"
+    assert opts.window_type == "hanning"
+
+    opts.round_to_power_of_two = False
+    assert opts.round_to_power_of_two is False
+
+    opts.blackman_coeff = 0.25
+    assert opts.blackman_coeff == 0.25
+
+    opts.snip_edges = False
+    assert opts.snip_edges is False
+
+
+def test_from_empty_dict():
+    opts = knf.FrameExtractionOptions.from_dict({})
+    opts2 = knf.FrameExtractionOptions()
+
+    assert str(opts) == str(opts2)
+
+
+def test_from_dict_partial():
+    d = {"samp_freq": 10, "frame_shift_ms": 2}
+
+    opts = knf.FrameExtractionOptions.from_dict(d)
+
+    opts2 = knf.FrameExtractionOptions()
+    assert str(opts) != str(opts2)
+
+    opts2.samp_freq = 10
+    assert str(opts) != str(opts2)
+
+    opts2.frame_shift_ms = 2
+    assert str(opts) == str(opts2)
+
+    opts2.frame_shift_ms = 3
+    assert str(opts) != str(opts2)
+
+
+def test_from_dict_full_and_as_dict():
+    opts = knf.FrameExtractionOptions()
+    opts.samp_freq = 20
+    opts.frame_length_ms = 100
+
+    d = opts.as_dict()
+    for key, value in d.items():
+        assert value == getattr(opts, key)
+
+    opts2 = knf.FrameExtractionOptions.from_dict(d)
+    assert str(opts2) == str(opts)
+
+    d["window_type"] = "hanning"
+    opts3 = knf.FrameExtractionOptions.from_dict(d)
+    assert opts3.window_type == "hanning"
+
+
+def test_pickle():
+    opts = knf.FrameExtractionOptions()
+    opts.samp_freq = 44100
+    opts.dither = 5.5
+    data = pickle.dumps(opts)
+
+    opts2 = pickle.loads(data)
+    assert str(opts) == str(opts2)
+
+
+def main():
+    test_default()
+    test_set_get()
+    test_from_empty_dict()
+    test_from_dict_partial()
+    test_from_dict_full_and_as_dict()
+    test_pickle()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_mel_bank_options.py b/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_mel_bank_options.py
new file mode 100755 (executable)
index 0000000..cd10376
--- /dev/null
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+#
+# Copyright (c)  2021  Xiaomi Corporation (authors: Fangjun Kuang)
+
+import pickle
+
+import kaldi_native_fbank as knf
+
+
+def test_default():
+    opts = knf.MelBanksOptions()
+    assert opts.num_bins == 25
+    assert opts.low_freq == 20
+    assert opts.high_freq == 0
+    assert opts.vtln_low == 100
+    assert opts.vtln_high == -500
+    assert opts.debug_mel is False
+    assert opts.htk_mode is False
+    assert opts.is_librosa is False
+    assert opts.norm == "slaney"
+
+
+def test_set_get():
+    opts = knf.MelBanksOptions()
+    opts.num_bins = 100
+    assert opts.num_bins == 100
+
+    opts.low_freq = 22
+    assert opts.low_freq == 22
+
+    opts.high_freq = 1
+    assert opts.high_freq == 1
+
+    opts.vtln_low = 101
+    assert opts.vtln_low == 101
+
+    opts.vtln_high = -100
+    assert opts.vtln_high == -100
+
+    opts.debug_mel = True
+    assert opts.debug_mel is True
+
+    opts.htk_mode = True
+    assert opts.htk_mode is True
+
+    opts.is_librosa = True
+    assert opts.is_librosa is True
+
+    opts.norm = "slaney"
+    assert opts.norm == "slaney"
+
+
+def test_from_empty_dict():
+    opts = knf.MelBanksOptions.from_dict({})
+    opts2 = knf.MelBanksOptions()
+
+    assert str(opts) == str(opts2)
+
+
+def test_from_dict_partial():
+    d = {"num_bins": 10, "debug_mel": True}
+
+    opts = knf.MelBanksOptions.from_dict(d)
+
+    opts2 = knf.MelBanksOptions()
+    assert str(opts) != str(opts2)
+
+    opts2.num_bins = 10
+    assert str(opts) != str(opts2)
+
+    opts2.debug_mel = True
+    assert str(opts) == str(opts2)
+
+    opts2.debug_mel = False
+    assert str(opts) != str(opts2)
+
+
+def test_from_dict_full_and_as_dict():
+    opts = knf.MelBanksOptions()
+    opts.num_bins = 80
+    opts.vtln_high = 2
+
+    d = opts.as_dict()
+    for key, value in d.items():
+        assert value == getattr(opts, key)
+
+    opts2 = knf.MelBanksOptions.from_dict(d)
+    assert str(opts2) == str(opts)
+
+    d["htk_mode"] = True
+    opts3 = knf.MelBanksOptions.from_dict(d)
+    assert opts3.htk_mode is True
+
+
+def test_pickle():
+    opts = knf.MelBanksOptions()
+    opts.num_bins = 100
+    opts.low_freq = 22
+    data = pickle.dumps(opts)
+
+    opts2 = pickle.loads(data)
+    assert str(opts) == str(opts2)
+
+
+def main():
+    test_default()
+    test_set_get()
+    test_from_empty_dict()
+    test_from_dict_partial()
+    test_from_dict_full_and_as_dict()
+    test_pickle()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_online_fbank.py b/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_online_fbank.py
new file mode 100755 (executable)
index 0000000..12f2c66
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+
+import sys
+
+try:
+    import kaldifeat
+except:
+    print("Please install kaldifeat first")
+    sys.exit(0)
+
+import kaldi_native_fbank as knf
+import torch
+
+
+def main():
+    sampling_rate = 16000
+    samples = torch.randn(16000 * 10)
+
+    opts = kaldifeat.FbankOptions()
+    opts.frame_opts.dither = 0
+    opts.mel_opts.num_bins = 80
+    opts.frame_opts.snip_edges = False
+    opts.mel_opts.debug_mel = False
+
+    online_fbank = kaldifeat.OnlineFbank(opts)
+
+    online_fbank.accept_waveform(sampling_rate, samples)
+
+    opts = knf.FbankOptions()
+    opts.frame_opts.dither = 0
+    opts.mel_opts.num_bins = 80
+    opts.frame_opts.snip_edges = False
+    opts.mel_opts.debug_mel = False
+
+    fbank = knf.OnlineFbank(opts)
+    fbank.accept_waveform(sampling_rate, samples.tolist())
+
+    assert online_fbank.num_frames_ready == fbank.num_frames_ready
+    for i in range(fbank.num_frames_ready):
+        f1 = online_fbank.get_frame(i)
+        f2 = torch.from_numpy(fbank.get_frame(i))
+        assert torch.allclose(f1, f2, atol=1e-3), (i, (f1 - f2).abs().max())
+
+
+if __name__ == "__main__":
+    torch.manual_seed(20220825)
+    main()
+    print("success")
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_online_mfcc.py b/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_online_mfcc.py
new file mode 100755 (executable)
index 0000000..794da95
--- /dev/null
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+import sys
+
+try:
+    import kaldifeat
+except:
+    print("Please install kaldifeat first")
+    sys.exit(0)
+
+import kaldi_native_fbank as knf
+import torch
+
+
+def main():
+    sampling_rate = 16000
+    samples = torch.randn(sampling_rate * 10)
+
+    opts = kaldifeat.MfccOptions()
+    opts.frame_opts.dither = 0
+    opts.num_ceps = 40
+    opts.mel_opts.num_bins = 40
+    opts.mel_opts.high_freq = -200
+    opts.frame_opts.snip_edges = False
+    online_mfcc = kaldifeat.OnlineMfcc(opts)
+
+    online_mfcc.accept_waveform(sampling_rate, samples)
+
+    opts = knf.MfccOptions()
+    opts.frame_opts.dither = 0
+    opts.num_ceps = 40
+    opts.mel_opts.num_bins = 40
+    opts.mel_opts.high_freq = -200
+    opts.frame_opts.snip_edges = False
+
+    mfcc = knf.OnlineMfcc(opts)
+    mfcc.accept_waveform(sampling_rate, samples.tolist())
+
+    assert online_mfcc.num_frames_ready == mfcc.num_frames_ready
+    for i in range(mfcc.num_frames_ready):
+        f1 = online_mfcc.get_frame(i)
+        f2 = torch.from_numpy(mfcc.get_frame(i))
+        assert torch.allclose(f1, f2, atol=1e-3), (i, (f1 - f2).abs().max(), f1, f2)
+
+
+if __name__ == "__main__":
+    torch.manual_seed(20240603)
+    main()
+    print("success")
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_online_whisper_fbank.py b/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_online_whisper_fbank.py
new file mode 100755 (executable)
index 0000000..5219f4a
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+#
+# Copyright (c)  2023  Xiaomi Corporation (authors: Fangjun Kuang)
+
+import pickle
+
+import kaldi_native_fbank as knf
+import torch
+
+
+def test():
+    opts = knf.WhisperFeatureOptions()
+
+    # Use 128 for whisper large v3
+    opts.dim = 128
+    online_whisper_fbank = knf.OnlineWhisperFbank(opts)
+
+    audio = torch.rand(100000)
+    # audio should be normalized into the range [-1, 1]
+    print(audio.shape, audio.max(), audio.min())
+    online_whisper_fbank.accept_waveform(sampling_rate=16000, waveform=audio.numpy())
+    online_whisper_fbank.input_finished()
+    print(online_whisper_fbank.num_frames_ready)
+
+    features = []
+    for i in range(online_whisper_fbank.num_frames_ready):
+        f = online_whisper_fbank.get_frame(i)
+        f = torch.from_numpy(f)
+        features.append(f)
+
+    features = torch.stack(features)
+    print(features.shape)
+
+    log_spec = torch.clamp(features, min=1e-10).log10()
+    log_spec = torch.maximum(log_spec, log_spec.max() - 8.0)
+    mel = (log_spec + 4.0) / 4.0
+    target = 3000
+    mel = torch.nn.functional.pad(mel, (0, 0, 0, target - mel.shape[0]), "constant", 0)
+    mel = mel.t().unsqueeze(0)
+    print(mel.shape)
+
+    assert mel.shape == (1, opts.dim, 3000), mel.shape
+    # Now you can input 'mel' to whisper.encoder model
+
+
+def main():
+    test()
+
+
+if __name__ == "__main__":
+    torch.manual_seed(20240112)
+    main()
diff --git a/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_rfft.py b/cli/fbank/sys/knf/kaldi-native-fbank/python/tests/test_rfft.py
new file mode 100755 (executable)
index 0000000..8852483
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+#
+# Copyright (c)  2021-2023  Xiaomi Corporation (authors: Fangjun Kuang)
+
+
+import torch
+
+import kaldi_native_fbank as knf
+
+
+def test_rfft():
+    N = 12
+    t = torch.arange(N)
+    r = torch.fft.rfft(t)
+    assert len(r) == N // 2 + 1, (len(r), N // 2 + 1)
+
+    real = r.real
+    imag = r.imag
+    print(r)
+
+    k = t.tolist()
+    rfft = knf.Rfft(N)
+
+    p = rfft.compute(k)
+    print(p)
+    return
+
+    assert abs(p[0] - real[0]) < 1e-5, (p[0], real[0])
+    assert imag[0] == 0, imag[0]
+
+    assert abs(p[1] - real[-1]) < 1e-5, (p[1], real[-1])
+    assert imag[-1] == 0, imag[-1]
+
+    for i in range(1, N // 2):
+        assert abs(p[2 * i] - real[i]) < 1e-5, (p[2 * i], real[i])
+        # Note: the imaginary part is multiplied by negative 1
+        assert abs(p[2 * i + 1] - -1 * imag[i]) < 1e-5, (p[2 * i + 1], imag[i])
+
+
+def main():
+    test_rfft()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/cli/fbank/sys/knf/scripts/check_style_cpplint.sh b/cli/fbank/sys/knf/scripts/check_style_cpplint.sh
new file mode 100755 (executable)
index 0000000..2fc150e
--- /dev/null
@@ -0,0 +1,126 @@
+#!/bin/bash
+#
+# Copyright      2020  Mobvoi Inc. (authors: Fangjun Kuang)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Usage:
+#
+# (1) To check files of the last commit
+#  ./scripts/check_style_cpplint.sh
+#
+# (2) To check changed files not committed yet
+#  ./scripts/check_style_cpplint.sh 1
+#
+# (3) To check all files in the project
+#  ./scripts/check_style_cpplint.sh 2
+
+
+cpplint_version="1.5.4"
+cur_dir=$(cd $(dirname $BASH_SOURCE) && pwd)
+kaldi_native_fbank_dir=$(cd $cur_dir/.. && pwd)
+
+build_dir=$kaldi_native_fbank_dir/build
+mkdir -p $build_dir
+
+cpplint_src=$build_dir/cpplint-${cpplint_version}/cpplint.py
+
+if [ ! -d "$build_dir/cpplint-${cpplint_version}" ]; then
+  pushd $build_dir
+  if command -v wget &> /dev/null; then
+    wget https://github.com/cpplint/cpplint/archive/${cpplint_version}.tar.gz
+  elif command -v curl &> /dev/null; then
+    curl -O -SL https://github.com/cpplint/cpplint/archive/${cpplint_version}.tar.gz
+  else
+    echo "Please install wget or curl to download cpplint"
+    exit 1
+  fi
+  tar xf ${cpplint_version}.tar.gz
+  rm ${cpplint_version}.tar.gz
+
+  # cpplint will report the following error for: __host__ __device__ (
+  #
+  #     Extra space before ( in function call  [whitespace/parens] [4]
+  #
+  # the following patch disables the above error
+  sed -i "3490i\        not Search(r'__host__ __device__\\\s+\\\(', fncall) and" $cpplint_src
+  popd
+fi
+
+source $kaldi_native_fbank_dir/scripts/utils.sh
+
+# return true if the given file is a c++ source file
+# return false otherwise
+function is_source_code_file() {
+  case "$1" in
+    *.cc|*.h|*.cu)
+      echo true;;
+    *)
+      echo false;;
+  esac
+}
+
+function check_style() {
+  python3 $cpplint_src $1 || abort $1
+}
+
+function check_last_commit() {
+  files=$(git diff HEAD^1 --name-only --diff-filter=ACDMRUXB)
+  echo $files
+}
+
+function check_current_dir() {
+  files=$(git status -s -uno --porcelain | awk '{
+  if (NF == 4) {
+    # a file has been renamed
+    print $NF
+  } else {
+    print $2
+  }}')
+
+  echo $files
+}
+
+function do_check() {
+  case "$1" in
+    1)
+      echo "Check changed files"
+      files=$(check_current_dir)
+      ;;
+    2)
+      echo "Check all files"
+      files=$(find $kaldi_native_fbank_dir/kaldi-native-fbank -name "*.h" -o -name "*.cc" -o -name "*.cu")
+      ;;
+    *)
+      echo "Check last commit"
+      files=$(check_last_commit)
+      ;;
+  esac
+
+  for f in $files; do
+    need_check=$(is_source_code_file $f)
+    if $need_check; then
+      [[ -f $f ]] && check_style $f
+    fi
+  done
+}
+
+function main() {
+  do_check $1
+
+  ok "Great! Style check passed!"
+}
+
+cd $kaldi_native_fbank_dir
+
+main $1
diff --git a/cli/fbank/sys/knf/scripts/utils.sh b/cli/fbank/sys/knf/scripts/utils.sh
new file mode 100644 (file)
index 0000000..fb424a7
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+default='\033[0m'
+bold='\033[1m'
+red='\033[31m'
+green='\033[32m'
+
+function ok() {
+  printf "${bold}${green}[OK]${default} $1\n"
+}
+
+function error() {
+  printf "${bold}${red}[FAILED]${default} $1\n"
+}
+
+function abort() {
+  printf "${bold}${red}[FAILED]${default} $1\n"
+  exit 1
+}
diff --git a/cli/fbank/sys/knf/setup.py b/cli/fbank/sys/knf/setup.py
new file mode 100644 (file)
index 0000000..de3d8a2
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+#
+# Copyright (c)  2021  Xiaomi Corporation (author: Fangjun Kuang)
+
+import re
+
+import setuptools
+
+from cmake.cmake_extension import BuildExtension, bdist_wheel, cmake_extension
+
+
+def read_long_description():
+    with open("README.md", encoding="utf8") as f:
+        readme = f.read()
+    return readme
+
+
+def get_package_version():
+    with open("CMakeLists.txt") as f:
+        content = f.read()
+
+    match = re.search(r"set\(KALDI_NATIVE_FBANK_VERSION (.*)\)", content)
+    latest_version = match.group(1).strip('"')
+    return latest_version
+
+
+package_name = "kaldi-native-fbank"
+
+with open("kaldi-native-fbank/python/kaldi_native_fbank/__init__.py", "a") as f:
+    f.write(f"__version__ = '{get_package_version()}'\n")
+
+setuptools.setup(
+    name=package_name,
+    version=get_package_version(),
+    author="Fangjun Kuang",
+    author_email="csukuangfj@gmail.com",
+    package_dir={"kaldi_native_fbank": "kaldi-native-fbank/python/kaldi_native_fbank"},
+    packages=["kaldi_native_fbank"],
+    url="https://github.com/csukuangfj/kaldi-native-fbank",
+    long_description=read_long_description(),
+    long_description_content_type="text/markdown",
+    ext_modules=[cmake_extension("_kaldi_native_fbank")],
+    cmdclass={"build_ext": BuildExtension, "bdist_wheel": bdist_wheel},
+    zip_safe=False,
+    classifiers=[
+        "Programming Language :: C++",
+        "Programming Language :: Python",
+        "Programming Language :: Python :: 3",
+        "Topic :: Scientific/Engineering :: Artificial Intelligence",
+    ],
+    python_requires=">=3.6.0",
+    license="Apache licensed, as found in the LICENSE file",
+)
+
+# remove the line __version__ from kaldi-native-fbank/python/kaldi_native_fbank/__init__.py
+with open("kaldi-native-fbank/python/kaldi_native_fbank/__init__.py", "r") as f:
+    lines = f.readlines()
+
+with open("kaldi-native-fbank/python/kaldi_native_fbank/__init__.py", "w") as f:
+    for line in lines:
+        if "__version__" in line:
+            # skip __version__ = "x.x.x"
+            continue
+        f.write(line)
diff --git a/cli/fbank/sys/knf/toolchains/README.md b/cli/fbank/sys/knf/toolchains/README.md
new file mode 100644 (file)
index 0000000..41924c0
--- /dev/null
@@ -0,0 +1,11 @@
+# gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf.tar.xz
+
+Go to <https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-a/downloads/8-3-2019-03> to download the toolchain.
+
+```bash
+mkdir /ceph-fj/fangjun/software
+cd /ceph-fj/fangjun/software
+tar xvf /path/to/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf.tar.xz
+
+export PATH=/ceph-fj/fangjun/software/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf/bin:$PATH
+```
diff --git a/cli/fbank/sys/knf/toolchains/arm-linux-gnueabihf.toolchain.cmake b/cli/fbank/sys/knf/toolchains/arm-linux-gnueabihf.toolchain.cmake
new file mode 100644 (file)
index 0000000..abe1a22
--- /dev/null
@@ -0,0 +1,17 @@
+# Copied from https://github.com/Tencent/ncnn/blob/master/toolchains/arm-linux-gnueabihf.toolchain.cmake
+set(CMAKE_SYSTEM_NAME Linux)
+set(CMAKE_SYSTEM_PROCESSOR arm)
+
+set(CMAKE_C_COMPILER "arm-linux-gnueabihf-gcc")
+set(CMAKE_CXX_COMPILER "arm-linux-gnueabihf-g++")
+
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+set(CMAKE_C_FLAGS "-march=armv7-a -mfloat-abi=hard -mfpu=neon")
+set(CMAKE_CXX_FLAGS "-march=armv7-a -mfloat-abi=hard -mfpu=neon")
+
+# cache flags
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" CACHE STRING "c flags")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" CACHE STRING "c++ flags")
diff --git a/cli/fbank/sys/knfc/CMakeLists.txt b/cli/fbank/sys/knfc/CMakeLists.txt
new file mode 100644 (file)
index 0000000..74b42e3
--- /dev/null
@@ -0,0 +1,25 @@
+cmake_minimum_required(VERSION 3.10)
+
+project(knfc)
+
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED True)
+
+option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
+set(KALDI_NATIVE_FBANK_BUILD_PYTHON OFF CACHE BOOL "Disable Python build")
+set(KALDI_NATIVE_FBANK_BUILD_TESTS OFF CACHE BOOL "Disable Tests build")
+
+add_subdirectory(../knf ${CMAKE_BINARY_DIR}/knf)
+
+add_library(knfc STATIC knfc.cc)
+
+target_link_libraries(knfc PRIVATE kaldi-native-fbank-core)
+
+target_include_directories(knfc PRIVATE
+    ${KNF_LIB_PATH}/include
+    ../knf/kaldi-native-fbank/csrc
+    ../knf
+)
+
+install(TARGETS knfc
+        ARCHIVE DESTINATION lib)
\ No newline at end of file
diff --git a/cli/fbank/sys/knfc/knfc.cc b/cli/fbank/sys/knfc/knfc.cc
new file mode 100644 (file)
index 0000000..a82eaca
--- /dev/null
@@ -0,0 +1,48 @@
+#include "kaldi-native-fbank/csrc/feature-window.h"
+#include "kaldi-native-fbank/csrc/mel-computations.h"
+#include "kaldi-native-fbank/csrc/feature-fbank.h"
+#include "kaldi-native-fbank/csrc/online-feature.h"
+#include "knfc.h"
+#include <iostream>
+
+void DestroyFbankResult(FbankResult* result) {
+    if (result) {
+        delete[] result->frames;
+        result->frames = nullptr;
+        result->num_frames = 0;
+    }
+}
+
+FbankResult ComputeFbank(const float *waveform, int32_t waveform_size) {
+    knf::FbankOptions options;
+    options.htk_compat = true;
+    options.use_energy = false;
+
+    // Frame options
+    options.frame_opts.window_type = "hanning";
+    options.frame_opts.dither = 0.0;
+
+    // Mel options
+    options.mel_opts.num_bins = 128;
+
+    knf::OnlineFbank fbank(options);
+    fbank.AcceptWaveform(options.frame_opts.samp_freq, waveform, waveform_size);
+    fbank.InputFinished();
+
+    int32_t num_frames = fbank.NumFramesReady();
+    int32_t num_bins = options.mel_opts.num_bins;
+
+    // Allocate memory for the frames
+    float* frames = new float[num_frames * num_bins];
+
+    // Fill the frames
+    for (int32_t i = 0; i < num_frames; i++) {
+        const float* frame = fbank.GetFrame(i);
+        std::copy(frame, frame + num_bins, frames + i * num_bins);
+    }
+
+    FbankResult result;
+    result.frames = frames;
+    result.num_frames = num_frames;
+    return result;
+}
diff --git a/cli/fbank/sys/knfc/knfc.h b/cli/fbank/sys/knfc/knfc.h
new file mode 100644 (file)
index 0000000..3e4db75
--- /dev/null
@@ -0,0 +1,18 @@
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+    float* frames;
+    int32_t num_frames;
+} FbankResult;
+
+extern "C" void DestroyFbankResult(FbankResult* result);
+
+FbankResult ComputeFbank(const float *waveform, int32_t waveform_size);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/cli/fbank/sys/src/lib.rs b/cli/fbank/sys/src/lib.rs
new file mode 100644 (file)
index 0000000..a38a13a
--- /dev/null
@@ -0,0 +1,5 @@
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/cli/fbank/sys/wrapper.hpp b/cli/fbank/sys/wrapper.hpp
new file mode 100644 (file)
index 0000000..d7bb2d7
--- /dev/null
@@ -0,0 +1 @@
+#include "knfc/knfc.h"
\ No newline at end of file
diff --git a/cli/src/audio.rs b/cli/src/audio.rs
new file mode 100644 (file)
index 0000000..e639aa1
--- /dev/null
@@ -0,0 +1,53 @@
+use anyhow::{Context, Error, Result};
+use async_walkdir::DirEntry;
+use fon::{chan::Ch32, Audio};
+use itertools::Itertools;
+use knf_rs::compute_fbank;
+use rodio::{Decoder, Source};
+use std::io::Cursor;
+
+pub const SAMPLE_RATE: u32 = 16000;
+pub const NUM_FRAMES: usize = 1024;
+pub const NUM_MEL_BINS: usize = 128;
+
+pub async fn is_audio_file(entry: DirEntry) -> Result<bool> {
+    if let Some(mime) = mime_guess::from_path(entry.path()).first() {
+        if mime.type_() != "audio" {
+            return Ok(false);
+        }
+    }
+    Ok(true)
+}
+
+fn extract_samples(decoder: Decoder<Cursor<Vec<u8>>>) -> Result<(Box<[f32]>, u32)> {
+    let channels = decoder.channels() as usize;
+    let sample_rate = decoder.sample_rate();
+    let samples = decoder
+        .map(|sample| sample as f32 / i16::MAX as f32)
+        .chunks(channels)
+        .into_iter()
+        .map(|chunk| chunk.into_iter().sum::<f32>() / channels as f32)
+        .collect_vec()
+        .into_boxed_slice();
+    Ok((samples, sample_rate))
+}
+
+pub fn extract_audio(buffer: Vec<u8>) -> Result<Audio<Ch32, 1>> {
+    let cursor = Cursor::new(buffer);
+    let decoder = Decoder::new(cursor).with_context(|| "Failed to initialize decoder")?;
+    let (samples, sample_rate) =
+        extract_samples(decoder).with_context(|| "Failed to extract samples")?;
+    let audio = Audio::with_f32_buffer(sample_rate, samples);
+    Ok(audio)
+}
+
+pub fn resample(audio: &mut Audio<Ch32, 1>, target_sample_rate: u32) {
+    *audio = Audio::with_audio(target_sample_rate, audio);
+}
+
+pub fn create_fbank(audio: &mut Audio<Ch32, 1>) -> Result<Box<[[f32; NUM_MEL_BINS]]>> {
+    let samples = audio.as_f32_slice();
+    let mut fbank = compute_fbank(samples).map_err(|e| Error::msg(e.to_string()))?;
+    fbank.truncate(NUM_FRAMES);
+    Ok(fbank.into_boxed_slice())
+}
diff --git a/cli/src/main.rs b/cli/src/main.rs
new file mode 100644 (file)
index 0000000..76e0b1c
--- /dev/null
@@ -0,0 +1,164 @@
+use anyhow::{Context, Error, Result};
+use async_walkdir::{Filtering, WalkDir};
+use clap::{Parser, Subcommand};
+use spinoff::{spinners, Spinner};
+use std::path::{Path, PathBuf};
+use tokio::{
+    fs::{copy, rename},
+    sync::Semaphore,
+    task::JoinSet,
+};
+use tokio_stream::StreamExt;
+
+pub mod audio;
+use audio::is_audio_file;
+
+pub mod processing;
+use processing::{get_result_path, process, Label, ResultPathOptions};
+
+const MAX_OPEN_FILES: usize = 128;
+static PERMITS: Semaphore = Semaphore::const_new(MAX_OPEN_FILES);
+
+#[derive(Parser)]
+#[command(about, long_about = None, version)]
+struct Args {
+    #[command(subcommand)]
+    command: Option<Command>,
+
+    #[arg(required = true)]
+    path: PathBuf,
+
+    #[arg(short, long, default_value = "0.0.0.0:8000")]
+    address: String,
+}
+
+#[derive(Subcommand, Debug)]
+pub enum Command {
+    #[command()]
+    Copy {
+        #[arg(short, long)]
+        speech_dir: Option<PathBuf>,
+
+        #[arg(short, long)]
+        music_dir: Option<PathBuf>,
+
+        #[arg(short, long)]
+        noise_dir: Option<PathBuf>,
+    },
+
+    #[command()]
+    Move {
+        #[arg(short, long)]
+        speech_dir: Option<PathBuf>,
+
+        #[arg(short, long)]
+        music_dir: Option<PathBuf>,
+
+        #[arg(short, long)]
+        noise_dir: Option<PathBuf>,
+    },
+}
+
+impl Command {
+    async fn perform(&self, path: &Path, label: &Label, options: &ResultPathOptions) -> Result<()> {
+        if let Some(dir) = get_result_path(path, label, options) {
+            match self {
+                Command::Copy { .. } => {
+                    let _ = copy(path, &dir).await?;
+                    return Ok(());
+                }
+                Command::Move { .. } => {
+                    rename(path, &dir).await?;
+                    return Ok(());
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Error> {
+    let args = Args::parse();
+    let url = format!("http:/{}/", args.address);
+    let options = match args.command {
+        Some(Command::Copy {
+            ref speech_dir,
+            ref music_dir,
+            ref noise_dir,
+        }) => Some(ResultPathOptions {
+            speech_dir: speech_dir.clone(),
+            music_dir: music_dir.clone(),
+            noise_dir: noise_dir.clone(),
+        }),
+        Some(Command::Move {
+            ref speech_dir,
+            ref music_dir,
+            ref noise_dir,
+        }) => Some(ResultPathOptions {
+            speech_dir: speech_dir.clone(),
+            music_dir: music_dir.clone(),
+            noise_dir: noise_dir.clone(),
+        }),
+        _ => None,
+    };
+
+    let mut spinner = Spinner::new(spinners::Line, "Loading...", None);
+
+    if args.path.is_file() {
+        let permit = PERMITS
+            .acquire()
+            .await
+            .with_context(|| "Failed to acquire permit")?;
+        spinner.update_text(format!("Labelling {:?}", args.path));
+        let (path, label) = process(args.path.clone(), url.clone(), permit)
+            .await
+            .with_context(|| format!("Failed to process {:?}", args.path))?;
+        if let Some(ref command) = args.command {
+            command
+                .perform(&path, &label, &options.unwrap())
+                .await
+                .with_context(|| format!("failed to perform command {:?}", command))?;
+        } else {
+            spinner.stop_with_message(&format!("{:?}: {:?}", path, label));
+        }
+        return Ok(());
+    }
+
+    let mut entries = WalkDir::new(args.path).filter(|entry| async move {
+        if is_audio_file(entry).await.unwrap_or(false) {
+            Filtering::Continue
+        } else {
+            Filtering::Ignore
+        }
+    });
+    let mut jobs = JoinSet::new();
+    loop {
+        match entries.next().await {
+            Some(Ok(entry)) => {
+                let permit = PERMITS
+                    .acquire()
+                    .await
+                    .with_context(|| "Failed to acquire permit")?;
+                let path = entry.path();
+                spinner.update_text(format!("Labelling {:?}", path));
+                let future = process(path, url.clone(), permit);
+                jobs.spawn(future);
+            }
+            Some(Err(e)) => return Err(e.into()),
+            None => break,
+        };
+    }
+    spinner.stop();
+    while let Some(result) = jobs.join_next().await {
+        let (path, label) = result?.with_context(|| "Failed to label a file")?;
+        println!("{:?}: {:?}", path, label);
+        if let Some(ref command) = args.command {
+            command
+                .perform(&path, &label, &options.clone().unwrap())
+                .await
+                .with_context(|| format!("failed to perform command {:?}", command))?;
+        }
+    }
+    Ok(())
+}
diff --git a/cli/src/processing.rs b/cli/src/processing.rs
new file mode 100644 (file)
index 0000000..145b517
--- /dev/null
@@ -0,0 +1,119 @@
+use crate::audio::{create_fbank, extract_audio, resample, NUM_MEL_BINS, SAMPLE_RATE};
+use anyhow::{Context, Result};
+use byte_slice_cast::AsByteSlice;
+use reqwest::Client;
+use safetensors::{serialize, tensor::TensorView, Dtype};
+use serde::Deserialize;
+use std::{
+    collections::HashMap,
+    path::{Path, PathBuf},
+};
+use tokio::{
+    fs::File,
+    io::AsyncReadExt,
+    sync::SemaphorePermit,
+    task::{block_in_place, spawn_blocking},
+};
+
+#[derive(Debug, Clone, Copy, Deserialize)]
+pub enum Label {
+    Speech,
+    Music,
+    Noise,
+}
+
+async fn label<'a>(tensor: &'a TensorView<'a>, url: String) -> Result<Label> {
+    let bytes = block_in_place(|| -> Result<Vec<u8>> {
+        let tensors = HashMap::from([("fbank", tensor)]);
+        let bytes = serialize(tensors, &None).with_context(|| "Failed to serialize tensor")?;
+        Ok(bytes)
+    })?;
+
+    let response = Client::new()
+        .post(url.clone())
+        .body(bytes)
+        .send()
+        .await
+        .with_context(|| format!("Failed to send bytes to {}", url))?;
+    let text = response
+        .text()
+        .await
+        .with_context(|| "Failed to extract response body")?;
+    let label = serde_json::from_str(&text).with_context(|| "Failed to deserialize output")?;
+    Ok(label)
+}
+
+pub async fn process(
+    path: PathBuf,
+    url: String,
+    _permit: SemaphorePermit<'_>,
+) -> Result<(PathBuf, Label)> {
+    let name = path
+        .file_name()
+        .unwrap_or(path.as_os_str())
+        .to_string_lossy()
+        .to_string();
+    let mut file = File::open(&path)
+        .await
+        .with_context(|| format!("Failed to open {}", name))?;
+    let mut buffer = Vec::new();
+    file.read_to_end(&mut buffer)
+        .await
+        .with_context(|| format!("Failed to read file: {}", name))?;
+
+    let fbank = spawn_blocking(move || -> Result<Box<[[f32; NUM_MEL_BINS]]>> {
+        let mut audio = extract_audio(buffer).with_context(|| "Failed to extract audio")?;
+        resample(&mut audio, SAMPLE_RATE);
+        let fbank = create_fbank(&mut audio).with_context(|| "Failed to create filter bank")?;
+        Ok(fbank)
+    })
+    .await??;
+    let size = vec![fbank.len(), NUM_MEL_BINS];
+    let tensor = TensorView::new(Dtype::F32, size, fbank.as_byte_slice())
+        .with_context(|| "Failed to create tensor from fbank")?;
+
+    let label = label(&tensor, url)
+        .await
+        .with_context(|| format!("Failed to label {}", name))?;
+    Ok((path, label))
+}
+
+#[derive(Clone)]
+pub struct ResultPathOptions {
+    pub speech_dir: Option<PathBuf>,
+    pub music_dir: Option<PathBuf>,
+    pub noise_dir: Option<PathBuf>,
+}
+
+pub fn get_result_path(path: &Path, label: &Label, options: &ResultPathOptions) -> Option<PathBuf> {
+    let mut dir = match label {
+        Label::Speech => {
+            if let Some(ref dir) = options.speech_dir {
+                dir.clone()
+            } else {
+                return None;
+            }
+        }
+        Label::Music => {
+            if let Some(ref dir) = options.music_dir {
+                dir.clone()
+            } else {
+                return None;
+            }
+        }
+        Label::Noise => {
+            if let Some(ref dir) = options.noise_dir {
+                dir.clone()
+            } else {
+                return None;
+            }
+        }
+    };
+    let name = path
+        .file_name()
+        .unwrap_or(path.as_os_str())
+        .to_string_lossy()
+        .to_string();
+    dir.push(name);
+    Some(dir)
+}
diff --git a/service/.gitignore b/service/.gitignore
new file mode 100644 (file)
index 0000000..b83d222
--- /dev/null
@@ -0,0 +1 @@
+/target/
diff --git a/service/Cargo.lock b/service/Cargo.lock
new file mode 100644 (file)
index 0000000..36861d1
--- /dev/null
@@ -0,0 +1,1803 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
+
+[[package]]
+name = "async-trait"
+version = "0.1.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "axum"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
+dependencies = [
+ "async-trait",
+ "axum-core",
+ "axum-macros",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper 1.0.1",
+ "tokio",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper 0.1.2",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
+
+[[package]]
+name = "bzip2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.11+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cc"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
+dependencies = [
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
+
+[[package]]
+name = "colored"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
+dependencies = [
+ "lazy_static",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
+[[package]]
+name = "flate2"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[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 = "gimli"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
+
+[[package]]
+name = "half"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "http"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "hyper"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "generic-array",
+]
+
+[[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.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "janitor-service"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "axum",
+ "clap",
+ "itertools",
+ "lazy_static",
+ "parse_duration",
+ "safetensors 0.4.4",
+ "serde",
+ "spinoff",
+ "tch",
+ "tokio",
+]
+
+[[package]]
+name = "jobserver"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
+dependencies = [
+ "libc",
+]
+
+[[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.158"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "matchit"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
+
+[[package]]
+name = "matrixmultiply"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a"
+dependencies = [
+ "autocfg",
+ "rawpointer",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "wasi",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ndarray"
+version = "0.15.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32"
+dependencies = [
+ "matrixmultiply",
+ "num-complex 0.4.6",
+ "num-integer",
+ "num-traits",
+ "rawpointer",
+]
+
+[[package]]
+name = "num"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
+dependencies = [
+ "num-bigint",
+ "num-complex 0.2.4",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[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-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[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-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
+dependencies = [
+ "autocfg",
+ "num-bigint",
+ "num-integer",
+ "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 = "object"
+version = "0.36.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "parse_duration"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d"
+dependencies = [
+ "lazy_static",
+ "num",
+ "regex",
+]
+
+[[package]]
+name = "password-hash"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
+dependencies = [
+ "base64ct",
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pbkdf2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+dependencies = [
+ "digest",
+ "hmac",
+ "password-hash",
+ "sha2",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+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 = "rawpointer"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
+[[package]]
+name = "ring"
+version = "0.17.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustls"
+version = "0.23.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
+dependencies = [
+ "log",
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "safetensors"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d93279b86b3de76f820a8854dd06cbc33cfa57a417b19c47f6a25280112fb1df"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "safetensors"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7725d4d98fa515472f43a6e2bbf956c48e06b89bb50593a040e5945160214450"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.208"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.208"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.125"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
+dependencies = [
+ "itoa",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "socket2"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "spinoff"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20aa2ed67fbb202e7b716ff8bfc6571dd9301617767380197d701c31124e88f6"
+dependencies = [
+ "colored",
+ "once_cell",
+ "paste",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
+
+[[package]]
+name = "tch"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3585f5bbf1ddf2498d7586bf870c7bb785a0bf1be09c54d0f93fce51d5f3c7fc"
+dependencies = [
+ "half",
+ "lazy_static",
+ "libc",
+ "ndarray",
+ "rand",
+ "safetensors 0.3.3",
+ "thiserror",
+ "torch-sys",
+ "zip",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+dependencies = [
+ "deranged",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "tinyvec"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.39.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "torch-sys"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef116d446d79bb2447748550baee86850d2d32d366cc9bdd4b217bdbe10cac63"
+dependencies = [
+ "anyhow",
+ "cc",
+ "libc",
+ "serde",
+ "serde_json",
+ "ureq",
+ "zip",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project",
+ "pin-project-lite",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "ureq"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a"
+dependencies = [
+ "base64",
+ "flate2",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "url",
+ "webpki-roots",
+]
+
+[[package]]
+name = "url"
+version = "2.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "webpki-roots"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[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.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[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.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[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.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[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.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[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.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[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.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+
+[[package]]
+name = "zip"
+version = "0.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
+dependencies = [
+ "aes",
+ "byteorder",
+ "bzip2",
+ "constant_time_eq",
+ "crc32fast",
+ "crossbeam-utils",
+ "flate2",
+ "hmac",
+ "pbkdf2",
+ "sha1",
+ "time",
+ "zstd",
+]
+
+[[package]]
+name = "zstd"
+version = "0.11.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "5.0.2+zstd.1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
+dependencies = [
+ "libc",
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.13+zstd.1.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
diff --git a/service/Cargo.toml b/service/Cargo.toml
new file mode 100644 (file)
index 0000000..6cefbf3
--- /dev/null
@@ -0,0 +1,17 @@
+[package]
+name = "janitor-service"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.86"
+clap = { version = "4.5.8", features = ["derive"] }
+parse_duration = "2.1.1"
+tokio = { version = "1.38.0", features = ["full"] }
+axum = { version = "0.7.5", features = ["macros"]  }
+safetensors = "0.4.4"
+itertools = "0.13.0"
+tch = { version = "0.17.0", features = ["download-libtorch"] }
+lazy_static = "1.5.0"
+spinoff = "0.8.0"
+serde = "1.0.204"
diff --git a/service/README.md b/service/README.md
new file mode 100644 (file)
index 0000000..670ec3e
--- /dev/null
@@ -0,0 +1,17 @@
+# Janitor Service
+
+Runs the labelling.
+
+## setup
+
+### model
+
+In order to build the model, you must have `conda` installed. Then, run `build_model.sh` to generate it.
+
+### building
+
+Run `cargo build --release` to compile for the CPU. If you have cuda, prepend the build command with `TORCH_CUDA_VERSION=cu118|cu121` depending on your version.
+
+### running
+
+You can see all the arguments with `cargo run -- --help`.
diff --git a/service/ast/.gitignore b/service/ast/.gitignore
new file mode 100644 (file)
index 0000000..906dc03
--- /dev/null
@@ -0,0 +1,3 @@
+/__pycache__/
+/pretrained_models/*
+/model.pt
diff --git a/service/ast/README.md b/service/ast/README.md
new file mode 100644 (file)
index 0000000..2588c6a
--- /dev/null
@@ -0,0 +1,3 @@
+# ast
+
+Forked from https://github.com/YuanGongND/ast/
diff --git a/service/ast/create.py b/service/ast/create.py
new file mode 100644 (file)
index 0000000..94c85ed
--- /dev/null
@@ -0,0 +1,25 @@
+from os import path
+from wget import download
+from torch import load, jit
+from model import ASTModel
+
+model = ASTModel()
+model.eval()
+
+checkpoint_path = "./pretrained_models/audioset_10_10_0.4593.pth"
+if not path.exists("./pretrained_models/audioset_10_10_0.4593.pth"):
+    audioset_mdl_url = (
+        "https://www.dropbox.com/s/cv4knew8mvbrnvq/audioset_0.4593.pth?dl=1"
+    )
+    download(
+        audioset_mdl_url,
+        out=checkpoint_path,
+    )
+checkpoint = load(checkpoint_path, weights_only=True)
+state_dict = {
+    key.replace("module.", ""): value for key, value in checkpoint.items()
+}
+model.load_state_dict(state_dict)
+
+torch_script_module = jit.script(model)
+torch_script_module.save("./model.pt")
diff --git a/service/ast/environment.yml b/service/ast/environment.yml
new file mode 100644 (file)
index 0000000..f1ec328
--- /dev/null
@@ -0,0 +1,10 @@
+name: ast
+channels:
+  - defaults
+dependencies:
+  - python
+  - pip
+  - pip:
+      - wget
+      - timm==0.4.5
+      - torch
diff --git a/service/ast/model.py b/service/ast/model.py
new file mode 100644 (file)
index 0000000..7d4879e
--- /dev/null
@@ -0,0 +1,360 @@
+import torch
+import torch.nn as nn
+import csv
+from torch.amp import autocast
+import os
+import wget
+import timm
+from timm.models.layers import to_2tuple, trunc_normal_
+
+os.environ["TORCH_HOME"] = "./pretrained_models"
+
+
+# override the timm package to relax the input shape constraint.
+class PatchEmbed(nn.Module):
+    def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
+        super().__init__()
+
+        img_size = to_2tuple(img_size)
+        patch_size = to_2tuple(patch_size)
+        num_patches = (img_size[1] // patch_size[1]) * (
+            img_size[0] // patch_size[0]
+        )
+        self.img_size = img_size
+        self.patch_size = patch_size
+        self.num_patches = num_patches
+
+        self.proj = nn.Conv2d(
+            in_chans, embed_dim, kernel_size=patch_size, stride=patch_size
+        )
+
+    def forward(self, x):
+        x = self.proj(x).flatten(2).transpose(1, 2)
+        return x
+
+
+class DistilledVisionTransformer:
+    def forward(self, x):
+        x, x_dist = self.forward_features(x)
+        x = self.head(x)
+        x_dist = self.head_dist(x_dist)
+        return (x + x_dist) / 2
+
+
+class ASTModel(nn.Module):
+    """
+    The AST model.
+    :param label_dim: the label dimension, i.e., the number of total classes, it is 527 for AudioSet, 50 for ESC-50, and 35 for speechcommands v2-35
+    :param fstride: the stride of patch spliting on the frequency dimension, for 16*16 patchs, fstride=16 means no overlap, fstride=10 means overlap of 6
+    :param tstride: the stride of patch spliting on the time dimension, for 16*16 patchs, tstride=16 means no overlap, tstride=10 means overlap of 6
+    :param input_fdim: the number of frequency bins of the input spectrogram
+    :param input_tdim: the number of time frames of the input spectrogram
+    :param imagenet_pretrain: if use ImageNet pretrained model
+    :param audioset_pretrain: if use full AudioSet and ImageNet pretrained model
+    :param model_size: the model size of AST, should be in [tiny224, small224, base224, base384], base224 and base 384 are same model, but are trained differently during ImageNet pretraining.
+    """
+
+    def __init__(
+        self,
+        label_dim=527,
+        fstride=10,
+        tstride=10,
+        input_fdim=128,
+        input_tdim=1024,
+        imagenet_pretrain=False,
+        audioset_pretrain=False,
+        model_size="base384",
+        verbose=True,
+    ):
+        super(ASTModel, self).__init__()
+        assert (
+            timm.__version__ == "0.4.5"
+        ), "Please use timm == 0.4.5, the code might not be compatible with newer versions."
+
+        if verbose == True:
+            print("---------------AST Model Summary---------------")
+            print(
+                "ImageNet pretraining: {:s}, AudioSet pretraining: {:s}".format(
+                    str(imagenet_pretrain), str(audioset_pretrain)
+                )
+            )
+        # override timm input shape restriction
+        timm.models.vision_transformer.PatchEmbed = PatchEmbed
+
+        # fix torchscript compilation
+        timm.models.vision_transformer.DistilledVisionTransformer.forward = (
+            DistilledVisionTransformer.forward
+        )
+
+        # if AudioSet pretraining is not used (but ImageNet pretraining may still apply)
+        if audioset_pretrain == False:
+            if model_size == "tiny224":
+                self.v = timm.create_model(
+                    "vit_deit_tiny_distilled_patch16_224",
+                    pretrained=imagenet_pretrain,
+                )
+            elif model_size == "small224":
+                self.v = timm.create_model(
+                    "vit_deit_small_distilled_patch16_224",
+                    pretrained=imagenet_pretrain,
+                )
+            elif model_size == "base224":
+                self.v = timm.create_model(
+                    "vit_deit_base_distilled_patch16_224",
+                    pretrained=imagenet_pretrain,
+                )
+            elif model_size == "base384":
+                self.v = timm.create_model(
+                    "vit_deit_base_distilled_patch16_384",
+                    pretrained=imagenet_pretrain,
+                )
+            else:
+                raise Exception(
+                    "Model size must be one of tiny224, small224, base224, base384."
+                )
+            self.original_num_patches = self.v.patch_embed.num_patches
+            self.oringal_hw = int(self.original_num_patches**0.5)
+            self.original_embedding_dim = self.v.pos_embed.shape[2]
+            self.mlp_head = nn.Sequential(
+                nn.LayerNorm(self.original_embedding_dim),
+                nn.Linear(self.original_embedding_dim, label_dim),
+            )
+
+            # automatcially get the intermediate shape
+            f_dim, t_dim = self.get_shape(
+                fstride, tstride, input_fdim, input_tdim
+            )
+            num_patches = f_dim * t_dim
+            self.v.patch_embed.num_patches = num_patches
+            if verbose == True:
+                print(
+                    "frequncey stride={:d}, time stride={:d}".format(
+                        fstride, tstride
+                    )
+                )
+                print("number of patches={:d}".format(num_patches))
+
+            # the linear projection layer
+            new_proj = torch.nn.Conv2d(
+                1,
+                self.original_embedding_dim,
+                kernel_size=(16, 16),
+                stride=(fstride, tstride),
+            )
+            if imagenet_pretrain == True:
+                new_proj.weight = torch.nn.Parameter(
+                    torch.sum(self.v.patch_embed.proj.weight, dim=1).unsqueeze(
+                        1
+                    )
+                )
+                new_proj.bias = self.v.patch_embed.proj.bias
+            self.v.patch_embed.proj = new_proj
+
+            # the positional embedding
+            if imagenet_pretrain == True:
+                # get the positional embedding from deit model, skip the first two tokens (cls token and distillation token), reshape it to original 2D shape (24*24).
+                new_pos_embed = (
+                    self.v.pos_embed[:, 2:, :]
+                    .detach()
+                    .reshape(
+                        1,
+                        self.original_num_patches,
+                        self.original_embedding_dim,
+                    )
+                    .transpose(1, 2)
+                    .reshape(
+                        1,
+                        self.original_embedding_dim,
+                        self.oringal_hw,
+                        self.oringal_hw,
+                    )
+                )
+                # cut (from middle) or interpolate the second dimension of the positional embedding
+                if t_dim <= self.oringal_hw:
+                    new_pos_embed = new_pos_embed[
+                        :,
+                        :,
+                        :,
+                        int(self.oringal_hw / 2)
+                        - int(t_dim / 2) : int(self.oringal_hw / 2)
+                        - int(t_dim / 2)
+                        + t_dim,
+                    ]
+                else:
+                    new_pos_embed = torch.nn.functional.interpolate(
+                        new_pos_embed,
+                        size=(self.oringal_hw, t_dim),
+                        mode="bilinear",
+                    )
+                # cut (from middle) or interpolate the first dimension of the positional embedding
+                if f_dim <= self.oringal_hw:
+                    new_pos_embed = new_pos_embed[
+                        :,
+                        :,
+                        int(self.oringal_hw / 2)
+                        - int(f_dim / 2) : int(self.oringal_hw / 2)
+                        - int(f_dim / 2)
+                        + f_dim,
+                        :,
+                    ]
+                else:
+                    new_pos_embed = torch.nn.functional.interpolate(
+                        new_pos_embed, size=(f_dim, t_dim), mode="bilinear"
+                    )
+                # flatten the positional embedding
+                new_pos_embed = new_pos_embed.reshape(
+                    1, self.original_embedding_dim, num_patches
+                ).transpose(1, 2)
+                # concatenate the above positional embedding with the cls token and distillation token of the deit model.
+                self.v.pos_embed = nn.Parameter(
+                    torch.cat(
+                        [self.v.pos_embed[:, :2, :].detach(), new_pos_embed],
+                        dim=1,
+                    )
+                )
+            else:
+                # if not use imagenet pretrained model, just randomly initialize a learnable positional embedding
+                # TODO can use sinusoidal positional embedding instead
+                new_pos_embed = nn.Parameter(
+                    torch.zeros(
+                        1,
+                        self.v.patch_embed.num_patches + 2,
+                        self.original_embedding_dim,
+                    )
+                )
+                self.v.pos_embed = new_pos_embed
+                trunc_normal_(self.v.pos_embed, std=0.02)
+
+        # now load a model that is pretrained on both ImageNet and AudioSet
+        elif audioset_pretrain == True:
+            if audioset_pretrain == True and imagenet_pretrain == False:
+                raise ValueError(
+                    "currently model pretrained on only audioset is not supported, please set imagenet_pretrain = True to use audioset pretrained model."
+                )
+            if model_size != "base384":
+                raise ValueError(
+                    "currently only has base384 AudioSet pretrained model."
+                )
+            device = torch.device(
+                "cuda" if torch.cuda.is_available() else "cpu"
+            )
+            if (
+                os.path.exists("./pretrained_models/audioset_10_10_0.4593.pth")
+                == False
+            ):
+                # this model performs 0.4593 mAP on the audioset eval set
+                audioset_mdl_url = "https://www.dropbox.com/s/cv4knew8mvbrnvq/audioset_0.4593.pth?dl=1"
+                wget.download(
+                    audioset_mdl_url,
+                    out="./pretrained_models/audioset_10_10_0.4593.pth",
+                )
+            sd = torch.load(
+                "./pretrained_models/audioset_10_10_0.4593.pth",
+                map_location=device,
+            )
+            audio_model = ASTModel(
+                label_dim=527,
+                fstride=10,
+                tstride=10,
+                input_fdim=128,
+                input_tdim=1024,
+                imagenet_pretrain=False,
+                audioset_pretrain=False,
+                model_size="base384",
+                verbose=False,
+            )
+            audio_model = torch.nn.DataParallel(audio_model)
+            audio_model.load_state_dict(sd, strict=False)
+            self.v = audio_model.module.v
+            self.original_embedding_dim = self.v.pos_embed.shape[2]
+            self.mlp_head = nn.Sequential(
+                nn.LayerNorm(self.original_embedding_dim),
+                nn.Linear(self.original_embedding_dim, label_dim),
+            )
+
+            f_dim, t_dim = self.get_shape(
+                fstride, tstride, input_fdim, input_tdim
+            )
+            num_patches = f_dim * t_dim
+            self.v.patch_embed.num_patches = num_patches
+            if verbose == True:
+                print(
+                    "frequncey stride={:d}, time stride={:d}".format(
+                        fstride, tstride
+                    )
+                )
+                print("number of patches={:d}".format(num_patches))
+
+            new_pos_embed = (
+                self.v.pos_embed[:, 2:, :]
+                .detach()
+                .reshape(1, 1212, 768)
+                .transpose(1, 2)
+                .reshape(1, 768, 12, 101)
+            )
+            # if the input sequence length is larger than the original audioset (10s), then cut the positional embedding
+            if t_dim < 101:
+                new_pos_embed = new_pos_embed[
+                    :, :, :, 50 - int(t_dim / 2) : 50 - int(t_dim / 2) + t_dim
+                ]
+            # otherwise interpolate
+            else:
+                new_pos_embed = torch.nn.functional.interpolate(
+                    new_pos_embed, size=(12, t_dim), mode="bilinear"
+                )
+            if f_dim < 12:
+                new_pos_embed = new_pos_embed[
+                    :, :, 6 - int(f_dim / 2) : 6 - int(f_dim / 2) + f_dim, :
+                ]
+            # otherwise interpolate
+            elif f_dim > 12:
+                new_pos_embed = torch.nn.functional.interpolate(
+                    new_pos_embed, size=(f_dim, t_dim), mode="bilinear"
+                )
+            new_pos_embed = new_pos_embed.reshape(
+                1, 768, num_patches
+            ).transpose(1, 2)
+            self.v.pos_embed = nn.Parameter(
+                torch.cat(
+                    [self.v.pos_embed[:, :2, :].detach(), new_pos_embed], dim=1
+                )
+            )
+
+    def get_shape(self, fstride, tstride, input_fdim=128, input_tdim=1024):
+        test_input = torch.randn(1, 1, input_fdim, input_tdim)
+        test_proj = nn.Conv2d(
+            1,
+            self.original_embedding_dim,
+            kernel_size=(16, 16),
+            stride=(fstride, tstride),
+        )
+        test_out = test_proj(test_input)
+        f_dim = test_out.shape[2]
+        t_dim = test_out.shape[3]
+        return f_dim, t_dim
+
+    @autocast("cuda")
+    def forward(self, x):
+        """
+        :param x: the input spectrogram, expected shape: (batch_size, time_frame_num, frequency_bins), e.g., (12, 1024, 128)
+        :return: prediction
+        """
+        # expect input x = (batch_size, time_frame_num, frequency_bins), e.g., (12, 1024, 128)
+        x = x.unsqueeze(1)
+        x = x.transpose(2, 3)
+
+        B = x.shape[0]
+        x = self.v.patch_embed(x)
+        cls_tokens = self.v.cls_token.expand(B, -1, -1)
+        dist_token = self.v.dist_token.expand(B, -1, -1)
+        x = torch.cat((cls_tokens, dist_token, x), dim=1)
+        x = x + self.v.pos_embed
+        x = self.v.pos_drop(x)
+        for blk in self.v.blocks:
+            x = blk(x)
+        x = self.v.norm(x)
+        x = (x[:, 0] + x[:, 1]) / 2
+
+        x = self.mlp_head(x)
+
+        return x
diff --git a/service/build_model.sh b/service/build_model.sh
new file mode 100755 (executable)
index 0000000..dd69784
--- /dev/null
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+eval "$(command conda 'shell.bash' 'hook' 2> /dev/null)"
+
+pushd ./ast/
+conda env create -f ./environment.yml
+conda activate ast
+python ./create.py
+conda deactivate
+popd
diff --git a/service/src/main.rs b/service/src/main.rs
new file mode 100644 (file)
index 0000000..e1dfb67
--- /dev/null
@@ -0,0 +1,69 @@
+use anyhow::Result;
+use axum::{body::Bytes, http::StatusCode, routing::post, serve, Json, Router};
+use clap::Parser;
+use parse_duration::parse;
+use queue::run;
+use safetensors::SafeTensors;
+use tensor::{fit, normalize, to_tensor};
+use tokio::{net::TcpListener, spawn, task::spawn_blocking};
+
+mod tensor;
+
+mod model;
+use model::{Label, Model};
+
+mod queue;
+
+#[derive(Debug, Parser)]
+#[command(about, long_about = None, version)]
+struct Args {
+    #[arg(short, long, default_value = "0.0.0.0:8000")]
+    address: String,
+
+    #[arg(short, long, default_value = "./ast/model.pt")]
+    model_path: String,
+
+    #[arg(short, long, default_value_t = 1)]
+    batch_size: usize,
+
+    #[arg(short, long, default_value = "100ms")]
+    timeout: String,
+}
+
+async fn handler(body: Bytes) -> Result<Json<Label>, (StatusCode, String)> {
+    let tensor = spawn_blocking(move || {
+        let tensors = SafeTensors::deserialize(&body[..])
+            .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
+        let fbank = tensors
+            .tensor("fbank")
+            .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
+        let mut tensor = to_tensor(fbank)?;
+        fit(&mut tensor)?;
+        normalize(&mut tensor)?;
+        Ok(tensor)
+    })
+    .await
+    .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))??;
+
+    let result_rx = queue::add(tensor).await;
+    let label = result_rx
+        .await
+        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
+    Ok(Json(label))
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    let args = Args::parse();
+
+    let timeout = parse(&args.timeout)?;
+    let model = Model::new(args.model_path)?;
+    spawn(async move {
+        run(model, args.batch_size, timeout).await;
+    });
+
+    let router = Router::new().route("/", post(handler));
+
+    let listener = TcpListener::bind(args.address).await?;
+    Ok(serve(listener, router).await?)
+}
diff --git a/service/src/model.rs b/service/src/model.rs
new file mode 100644 (file)
index 0000000..9f7dd8b
--- /dev/null
@@ -0,0 +1,58 @@
+use anyhow::Result;
+use itertools::Itertools;
+use serde::Serialize;
+use std::path::Path;
+use tch::{autocast, no_grad, CModule, Device, Tensor};
+
+#[derive(Debug, Clone, Copy, Serialize)]
+pub enum Label {
+    Speech,
+    Music,
+    Noise,
+}
+
+pub struct Model {
+    model: CModule,
+}
+
+impl Model {
+    pub fn new<T>(model_path: T) -> Result<Model>
+    where
+        T: AsRef<Path>,
+    {
+        let device = Device::cuda_if_available();
+        println!("Running model on {:?}", device);
+        Ok(Self {
+            model: CModule::load_on_device(model_path, device)?,
+        })
+    }
+
+    pub fn label(&self, tensor: &Tensor) -> Result<Box<[Label]>> {
+        let output = no_grad(|| autocast(true, || tensor.apply(&self.model))).f_sigmoid()?;
+        let labels = Vec::try_from(output.flatten(0, -1))?
+            .chunks(527)
+            .map(|chunk| {
+                let output: [f32; 527] = chunk.try_into().unwrap();
+                let results = [
+                    (Label::Speech, output[0]),
+                    (Label::Music, output[137]),
+                    (Label::Noise, output[513]),
+                ];
+                let label = {
+                    if results[0].1 < 0.5 && results[1].1 < 0.5 {
+                        Label::Noise
+                    } else {
+                        results
+                            .iter()
+                            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
+                            .map(|(label, _)| label.to_owned())
+                            .unwrap_or(Label::Noise)
+                    }
+                };
+                label
+            })
+            .collect_vec()
+            .into_boxed_slice();
+        Ok(labels)
+    }
+}
diff --git a/service/src/queue.rs b/service/src/queue.rs
new file mode 100644 (file)
index 0000000..4713f82
--- /dev/null
@@ -0,0 +1,89 @@
+use lazy_static::lazy_static;
+use spinoff::{spinners, Spinner};
+use std::{cmp::min, collections::VecDeque};
+use tch::Tensor;
+use tokio::{
+    sync::{
+        oneshot::{channel, Receiver, Sender},
+        Mutex,
+    },
+    time::{sleep, Duration, Instant},
+};
+
+use crate::model::{Label, Model};
+
+type JobQueue = VecDeque<(Tensor, Sender<Label>)>;
+
+lazy_static! {
+    static ref QUEUE: Mutex<JobQueue> = Mutex::new(VecDeque::new());
+}
+
+pub async fn add(tensor: Tensor) -> Receiver<Label> {
+    let (result_tx, result_rx) = channel();
+    let job = (tensor, result_tx);
+    QUEUE.lock().await.push_back(job);
+    result_rx
+}
+
+async fn get_jobs(
+    batch_size: usize,
+    timeout: Duration,
+) -> ((Vec<Tensor>, Vec<Sender<Label>>), usize) {
+    let mut tensors = Vec::with_capacity(batch_size);
+    let mut transmitters = Vec::with_capacity(batch_size);
+    let mut remaining = batch_size;
+    let mut rest = 0;
+    let start = Instant::now();
+    while remaining > 0 && Instant::now() - start < timeout {
+        let mut queue = QUEUE.lock().await;
+        let usable = min(remaining, queue.len());
+        if usable > 0 {
+            for _ in 0..usable {
+                if let Some(job) = queue.pop_front() {
+                    tensors.push(job.0);
+                    transmitters.push(job.1);
+                }
+            }
+            remaining -= usable;
+            rest = queue.len();
+        } else {
+            drop(queue);
+            sleep(Duration::from_millis(100)).await;
+        }
+    }
+    ((tensors, transmitters), rest)
+}
+
+pub async fn run(model: Model, batch_size: usize, timeout: Duration) {
+    let mut spinner = Spinner::new(spinners::Line, "Waiting for jobs", None);
+    loop {
+        let ((tensors, transmitters), remaining) = get_jobs(batch_size, timeout).await;
+        if tensors.is_empty() {
+            continue;
+        }
+
+        let tensor = match Tensor::f_stack(&tensors, 0) {
+            Ok(tensor) => tensor,
+            Err(_) => {
+                continue;
+            }
+        };
+
+        spinner.update_text(format!(
+            "Executing {} job(s) with {} still remaining",
+            tensors.len(),
+            remaining
+        ));
+        let labels = match model.label(&tensor) {
+            Ok(tensor) => tensor,
+            Err(_) => {
+                continue;
+            }
+        };
+
+        for (&label, result_tx) in labels.iter().zip(transmitters.into_iter()) {
+            _ = result_tx.send(label);
+        }
+        spinner.update_text("Waiting for jobs");
+    }
+}
diff --git a/service/src/tensor.rs b/service/src/tensor.rs
new file mode 100644 (file)
index 0000000..48a4c9b
--- /dev/null
@@ -0,0 +1,93 @@
+use std::cmp::Ordering;
+
+use anyhow::Result;
+use axum::http::StatusCode;
+use safetensors::{tensor::TensorView, Dtype};
+use tch::{Device, Kind, Tensor};
+
+pub const NUM_FRAMES: i64 = 1024;
+pub const NUM_MEL_BINS: i64 = 128;
+
+pub fn to_tensor(view: TensorView) -> Result<Tensor, (StatusCode, String)> {
+    let size: Vec<i64> = view.shape().iter().map(|&x| x as i64).collect();
+    let kind = match view.dtype() {
+        Dtype::BOOL => Kind::Bool,
+        Dtype::U8 => Kind::Uint8,
+        Dtype::I8 => Kind::Int8,
+        Dtype::I16 => Kind::Int16,
+        Dtype::I32 => Kind::Int,
+        Dtype::I64 => Kind::Int64,
+        Dtype::BF16 => Kind::BFloat16,
+        Dtype::F16 => Kind::Half,
+        Dtype::F32 => Kind::Float,
+        Dtype::F64 => Kind::Double,
+        _ => {
+            return Err((
+                StatusCode::BAD_REQUEST,
+                format!("unsupported dtype: {:?}", view.dtype()),
+            ))
+        }
+    };
+    let mut tensor = Tensor::f_from_data_size(view.data(), &size, kind)
+        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
+    check(&tensor)?;
+    tensor = tensor
+        .f_to(Device::cuda_if_available())
+        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
+    Ok(tensor)
+}
+
+pub fn check(tensor: &Tensor) -> Result<(), (StatusCode, String)> {
+    let kind = tensor
+        .f_kind()
+        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
+    if kind != Kind::Float {
+        return Err((
+            StatusCode::BAD_REQUEST,
+            "input must be a 32 bit float".to_string(),
+        ));
+    }
+
+    let (_, num_mel_bins) = tensor
+        .size2()
+        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
+    if num_mel_bins != NUM_MEL_BINS {
+        return Err((
+            StatusCode::BAD_REQUEST,
+            format!(
+                "Expected {} mel bins but got {}",
+                NUM_MEL_BINS, num_mel_bins
+            ),
+        ));
+    }
+    Ok(())
+}
+
+pub fn fit(tensor: &mut Tensor) -> Result<(), (StatusCode, String)> {
+    let (num_frames, _) = tensor
+        .size2()
+        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
+    match NUM_FRAMES.cmp(&num_frames) {
+        Ordering::Less => {
+            *tensor = tensor
+                .f_narrow(0, 0, NUM_FRAMES)
+                .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
+        }
+        Ordering::Greater => {
+            *tensor = tensor
+                .f_pad([0, 0, 0, NUM_FRAMES - num_frames], "constant", Some(0.0))
+                .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
+        }
+        _ => {}
+    }
+    Ok(())
+}
+
+pub fn normalize(tensor: &mut Tensor) -> Result<(), (StatusCode, String)> {
+    *tensor = tensor
+        .f_subtract_scalar(-4.2677393)
+        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
+        .f_divide_scalar(4.5689974 * 2.0)
+        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
+    Ok(())
+}