commit 3b8b160a07d5625fcd3301681103c8ad37546942 Author: Polochon-street Date: Fri May 14 16:35:08 2021 +0200 Just unpacked here 📦 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..6809f24 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,34 @@ +name: Rust + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2021-04-01 + override: false + - name: Packages + run: sudo apt-get install build-essential yasm libavutil-dev libavcodec-dev libavformat-dev libavfilter-dev libavfilter-dev libavdevice-dev libswresample-dev libfftw3-dev ffmpeg + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose + - name: Run example tests + run: cargo test --verbose --examples + - name: Build benches + run: cargo +nightly-2021-04-01 bench --verbose --features=bench --no-run + - name: Build examples + run: cargo build --examples --verbose diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b5d9c49 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1860 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "aubio-lib" +version = "0.1.3" +source = "git+https://github.com/Polochon-street/aubio-rs?branch=aubio-tweaks#46d3b7e70f35e5f4c3b0e5c53b96dc5a0489bdce" +dependencies = [ + "aubio-sys 0.1.3 (git+https://github.com/Polochon-street/aubio-rs?branch=aubio-tweaks)", + "fetch_unroll", + "which", +] + +[[package]] +name = "aubio-rs" +version = "0.1.3" +source = "git+https://github.com/Polochon-street/aubio-rs#81757678e3a89716a490dbb5f3d69c7f53183306" +dependencies = [ + "aubio-sys 0.1.3 (git+https://github.com/Polochon-street/aubio-rs)", +] + +[[package]] +name = "aubio-sys" +version = "0.1.3" +source = "git+https://github.com/Polochon-street/aubio-rs?branch=aubio-tweaks#46d3b7e70f35e5f4c3b0e5c53b96dc5a0489bdce" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "aubio-sys" +version = "0.1.3" +source = "git+https://github.com/Polochon-street/aubio-rs#81757678e3a89716a490dbb5f3d69c7f53183306" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bindgen" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c0bb6167449588ff70803f4127f0684f9063097eca5016f37eb52b92c2cf36" +dependencies = [ + "bitflags", + "cexpr", + "cfg-if 0.1.10", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bliss-rs" +version = "0.1.0" +dependencies = [ + "anyhow", + "aubio-lib", + "aubio-rs", + "clap", + "crossbeam", + "dirs", + "env_logger", + "ffmpeg-next", + "lazy_static", + "log", + "mpd", + "ndarray", + "ndarray-npy", + "ndarray-stats", + "noisy_float", + "num_cpus", + "rayon", + "ripemd160", + "rusqlite", + "rustfft", + "tempdir", + "thiserror", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bufstream" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" + +[[package]] +name = "bumpalo" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chunked_transfer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" + +[[package]] +name = "clang-sys" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "const_fn" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402da840495de3f976eaefc3485b7f5eb5b0bf9761f9a47be27fe975b3b8c2ec" + +[[package]] +name = "cookie" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" +dependencies = [ + "percent-encoding", + "time 0.2.26", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3" +dependencies = [ + "cookie", + "idna", + "log", + "publicsuffix", + "serde", + "serde_json", + "time 0.2.26", + "url", +] + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd01a6eb3daaafa260f6fc94c3a6c36390abc2080e38e3e34ced87393fb77d80" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "env_logger" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fetch_unroll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d44807d562d137f063cbfe209da1c3f9f2fa8375e11166ef495daab7b847f9" +dependencies = [ + "libflate", + "tar", + "ureq", +] + +[[package]] +name = "ffmpeg-next" +version = "4.4.0-dev" +source = "git+https://github.com/Polochon-street/rust-ffmpeg?branch=stop-failing-unimplemented-arms#fe5ed5e0d3a86ca292e2e2b9bb00e6b8ac7f3185" +dependencies = [ + "bitflags", + "ffmpeg-sys-next", + "libc", +] + +[[package]] +name = "ffmpeg-sys-next" +version = "4.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fde8cbf91a1b044b86d9e9e944c33806a68f5e34e4281033594ceaab47a3746" +dependencies = [ + "bindgen", + "cc", + "libc", + "num_cpus", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "filetime" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi", +] + +[[package]] +name = "flate2" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "js-sys" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[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.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" + +[[package]] +name = "libflate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d87eae36b3f680f7f01645121b782798b56ef33c53f83d1c66ba3a22b60bfe3" +dependencies = [ + "adler32", + "crc32fast", + "libflate_lz77", +] + +[[package]] +name = "libflate_lz77" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a734c0493409afcd49deee13c006a04e3586b9761a03543c6272c9c51f2f5a" +dependencies = [ + "rle-decode-fast", +] + +[[package]] +name = "libloading" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +dependencies = [ + "cc", + "winapi", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19cb1effde5f834799ac5e5ef0e40d45027cd74f271b1de786ba8abb30e2164d" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "matrixmultiply" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a8a15b776d9dfaecd44b03c5828c2199cddff5247215858aac14624f8d6b741" +dependencies = [ + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "memoffset" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mpd" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a20784da57fa01bf7910a5da686d9f39ff37feaa774856b71f050e4331bf82" +dependencies = [ + "bufstream", + "rustc-serialize", + "time 0.1.43", +] + +[[package]] +name = "ndarray" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1372704f14bb132a49a6701c2238970a359ee0829fed481b522a63bf25456a" +dependencies = [ + "matrixmultiply", + "num-complex 0.4.0", + "num-integer", + "num-traits", + "rawpointer", + "rayon", +] + +[[package]] +name = "ndarray-npy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a3dd44155949fd34abacc49ceca5d639a4f65ffd5d461b20b71a5a64981143" +dependencies = [ + "byteorder", + "ndarray", + "num-traits", + "py_literal", + "zip", +] + +[[package]] +name = "ndarray-stats" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22877ad014bafa2f7dcfa5d556b0c7a52b0546cc98061a1ebef6d1834957b069" +dependencies = [ + "indexmap", + "itertools", + "ndarray", + "noisy_float", + "num-integer", + "num-traits", + "rand 0.8.3", +] + +[[package]] +name = "noisy_float" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978fe6e6ebc0bf53de533cd456ca2d9de13de13856eda1518a285d7705a213af" +dependencies = [ + "num-traits", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "num-bigint" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "primal-check" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01419cee72c1a1ca944554e23d83e483e1bccf378753344e881de28b5487511d" +dependencies = [ + "num-integer", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "publicsuffix" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b4ce31ff0a27d93c8de1849cf58162283752f065a90d508f1105fa6c9a213f" +dependencies = [ + "idna", + "url", +] + +[[package]] +name = "py_literal" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102df7a3d46db9d3891f178dcc826dc270a6746277a9ae6436f8d29fd490a8e1" +dependencies = [ + "num-bigint", + "num-complex 0.4.0", + "num-traits", + "pest", + "pest_derive", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.2", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.2", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core 0.6.2", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "ripemd160" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "rle-decode-fast" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" + +[[package]] +name = "rusqlite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc783b7ddae608338003bac1fa00b6786a75a9675fbd8e87243ecfdea3f6ed2" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "smallvec", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustfft" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87c289af25ed4b3e258126559b6603e5a8f734f48defbbe0fd62abdb49089c3" +dependencies = [ + "num-complex 0.3.1", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] +name = "strength_reduce" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ff2f71c82567c565ba4b3009a9350a96a7269eaa4001ebedae926230bc2254" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tar" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0bcfbd6a598361fda270d82469fff3d65089dc33e175c9a131f7b4cd395f228" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "time" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a8cbfbf47955132d0202d1662f49b2423ae35862aee471f3ba4b133358f372" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "transpose" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95f9c900aa98b6ea43aee227fd680550cdec726526aab8ac801549eadb25e39f" +dependencies = [ + "num-integer", + "strength_reduce", +] + +[[package]] +name = "typenum" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-bidi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "294b85ef5dbc3670a72e82a89971608a1fcc4ed5c7c5a2895230d31a95f0569b" +dependencies = [ + "base64", + "chunked_transfer", + "cookie", + "cookie_store", + "log", + "once_cell", + "qstring", + "rustls", + "url", + "webpki", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" + +[[package]] +name = "web-sys" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + +[[package]] +name = "which" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" +dependencies = [ + "either", + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "xattr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +dependencies = [ + "libc", +] + +[[package]] +name = "zip" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c83dc9b784d252127720168abd71ea82bf8c3d96b17dc565b5e2a02854f2b27" +dependencies = [ + "byteorder", + "crc32fast", + "flate2", + "thiserror", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..202a8ce --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "bliss-rs" +version = "0.1.0" +authors = ["Polochon-street "] +edition = "2018" + +[features] +build-ffmpeg = ["ffmpeg-next/build"] +bench = [] + +[dependencies] +ripemd160 = "0.9.0" +ndarray-npy = "0.8.0" +ndarray = { version = "0.15.0", features = ["rayon"] } +num_cpus = "1.13.0" +ndarray-stats = "0.5.0" +rustfft = "5.0.1" +lazy_static = "1.4.0" +rayon = "1.5.0" +crossbeam = "0.8.0" +noisy_float = "0.2.0" +# Until either https://github.com/zmwangx/rust-ffmpeg/pull/60 +# or https://github.com/zmwangx/rust-ffmpeg/pull/62 is in +ffmpeg-next = {git = "https://github.com/Polochon-street/rust-ffmpeg", branch = "stop-failing-unimplemented-arms"} +log = "0.4.14" +env_logger = "0.8.3" +thiserror = "1.0.24" + +[dev-dependencies] +mpd = "0.0.12" +rusqlite = "0.25.0" +dirs = "3.0.1" +tempdir = "0.3.7" +clap = "2.33.3" +anyhow = "1.0.40" + +[dependencies.aubio-rs] +git = "https://github.com/Polochon-street/aubio-rs" + +# Like this until https://github.com/aubio/aubio/issues/336 is solved one way +# or the other +[dependencies.aubio-lib] +git = "https://github.com/Polochon-street/aubio-rs" +branch = "aubio-tweaks" diff --git a/README.md b/README.md new file mode 100644 index 0000000..814fe48 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +![build](https://github.com/Polochon-street/bliss-rs/workflows/Rust/badge.svg) + +# Bliss music analyser - Rust version +Bliss-rs is the Rust improvement of [Bliss](https://github.com/Polochon-street/bliss). The data it +outputs is not compatible with the ones used by Bliss, since it uses +different, more accurate features, based on actual literature this time. + +Like Bliss, it ease the creation of « intelligent » playlists and/or continuous +play, à la Spotify/Grooveshark Radio. + +## Usage +Currently not usable. + +It's missing a binary / example to actually use the computed features. + +## Acknowledgements + +* This library relies heavily on [aubio](https://aubio.org/)'s + [Rust bindings](https://crates.io/crates/aubio-rs) for the spectral / + timbral analysis, so a big thanks to both the creators and contributors + of librosa, and to @katyo for making aubio bindings for Rust. +* The first part of the chroma extraction is basically a rewrite of + [librosa](https://librosa.org/doc/latest/index.html)'s + [chroma feature extraction](https://librosa.org/doc/latest/generated/librosa.feature.chroma_stft.html?highlight=chroma#librosa.feature.chroma_stftfrom) + from python to Rust, with just as little features as needed. Thanks + to both creators and contributors as well. +* Finally, a big thanks to + [Christof Weiss](https://www.audiolabs-erlangen.de/fau/assistant/weiss) + for pointing me in the right direction for the chroma feature summarization, + which are basically also a rewrite from Python to Rust of some of the + awesome notebooks by AudioLabs Erlangen, that you can find + [here](https://www.audiolabs-erlangen.de/resources/MIR/FMP/C0/C0.html). diff --git a/data/capacity_fix.ogg b/data/capacity_fix.ogg new file mode 100644 index 0000000..15528c2 Binary files /dev/null and b/data/capacity_fix.ogg differ diff --git a/data/chroma-filter.npy b/data/chroma-filter.npy new file mode 100644 index 0000000..b70466b Binary files /dev/null and b/data/chroma-filter.npy differ diff --git a/data/chroma-interval.npy b/data/chroma-interval.npy new file mode 100644 index 0000000..af02fbc Binary files /dev/null and b/data/chroma-interval.npy differ diff --git a/data/chroma-stft-normalized-expected.npy b/data/chroma-stft-normalized-expected.npy new file mode 100644 index 0000000..abd038d Binary files /dev/null and b/data/chroma-stft-normalized-expected.npy differ diff --git a/data/chroma.npy b/data/chroma.npy new file mode 100644 index 0000000..2da5d56 Binary files /dev/null and b/data/chroma.npy differ diff --git a/data/convolve.npy b/data/convolve.npy new file mode 100644 index 0000000..86931d7 Binary files /dev/null and b/data/convolve.npy differ diff --git a/data/convolve_odd.npy b/data/convolve_odd.npy new file mode 100644 index 0000000..e3a557a Binary files /dev/null and b/data/convolve_odd.npy differ diff --git a/data/downsampled.npy b/data/downsampled.npy new file mode 100644 index 0000000..54f8768 Binary files /dev/null and b/data/downsampled.npy differ diff --git a/data/estimate-tuning-pitch.npy b/data/estimate-tuning-pitch.npy new file mode 100644 index 0000000..633f845 Binary files /dev/null and b/data/estimate-tuning-pitch.npy differ diff --git a/data/f_analysis.npy b/data/f_analysis.npy new file mode 100644 index 0000000..5d72020 Binary files /dev/null and b/data/f_analysis.npy differ diff --git a/data/f_analysis_normalized.npy b/data/f_analysis_normalized.npy new file mode 100644 index 0000000..b5b4cf9 Binary files /dev/null and b/data/f_analysis_normalized.npy differ diff --git a/data/filtered_features.npy b/data/filtered_features.npy new file mode 100644 index 0000000..564837f Binary files /dev/null and b/data/filtered_features.npy differ diff --git a/data/interval-feature-matrix.npy b/data/interval-feature-matrix.npy new file mode 100644 index 0000000..23e826f Binary files /dev/null and b/data/interval-feature-matrix.npy differ diff --git a/data/librosa-decoded.npy b/data/librosa-decoded.npy new file mode 100644 index 0000000..9d4c90c Binary files /dev/null and b/data/librosa-decoded.npy differ diff --git a/data/librosa-stft.npy b/data/librosa-stft.npy new file mode 100644 index 0000000..a0cc968 Binary files /dev/null and b/data/librosa-stft.npy differ diff --git a/data/no_channel.wav b/data/no_channel.wav new file mode 100644 index 0000000..c2cff9c Binary files /dev/null and b/data/no_channel.wav differ diff --git a/data/piano.flac b/data/piano.flac new file mode 100644 index 0000000..9cc0c6f Binary files /dev/null and b/data/piano.flac differ diff --git a/data/picture.jpg b/data/picture.jpg new file mode 100644 index 0000000..f480cb1 Binary files /dev/null and b/data/picture.jpg differ diff --git a/data/picture.png b/data/picture.png new file mode 100644 index 0000000..f480cb1 Binary files /dev/null and b/data/picture.png differ diff --git a/data/pitch-tuning.npy b/data/pitch-tuning.npy new file mode 100644 index 0000000..633f845 Binary files /dev/null and b/data/pitch-tuning.npy differ diff --git a/data/s16_mono_22_5kHz.flac b/data/s16_mono_22_5kHz.flac new file mode 100644 index 0000000..7cc9bf4 Binary files /dev/null and b/data/s16_mono_22_5kHz.flac differ diff --git a/data/s16_stereo_22_5kHz.flac b/data/s16_stereo_22_5kHz.flac new file mode 100644 index 0000000..08ba536 Binary files /dev/null and b/data/s16_stereo_22_5kHz.flac differ diff --git a/data/s32_stereo_44_1_kHz.flac b/data/s32_stereo_44_1_kHz.flac new file mode 100644 index 0000000..b83246d Binary files /dev/null and b/data/s32_stereo_44_1_kHz.flac differ diff --git a/data/s32_stereo_44_1_kHz.mp3 b/data/s32_stereo_44_1_kHz.mp3 new file mode 100644 index 0000000..f4a0eb7 Binary files /dev/null and b/data/s32_stereo_44_1_kHz.mp3 differ diff --git a/data/sorted_features.npy b/data/sorted_features.npy new file mode 100644 index 0000000..20081e4 Binary files /dev/null and b/data/sorted_features.npy differ diff --git a/data/spectrum-chroma-mags.npy b/data/spectrum-chroma-mags.npy new file mode 100644 index 0000000..0277701 Binary files /dev/null and b/data/spectrum-chroma-mags.npy differ diff --git a/data/spectrum-chroma-pitches.npy b/data/spectrum-chroma-pitches.npy new file mode 100644 index 0000000..94155e5 Binary files /dev/null and b/data/spectrum-chroma-pitches.npy differ diff --git a/data/spectrum-chroma.npy b/data/spectrum-chroma.npy new file mode 100644 index 0000000..1101726 Binary files /dev/null and b/data/spectrum-chroma.npy differ diff --git a/data/tone_11080Hz.flac b/data/tone_11080Hz.flac new file mode 100644 index 0000000..59bdda6 Binary files /dev/null and b/data/tone_11080Hz.flac differ diff --git a/data/white_noise.flac b/data/white_noise.flac new file mode 100644 index 0000000..b143515 Binary files /dev/null and b/data/white_noise.flac differ diff --git a/examples/analyse.rs b/examples/analyse.rs new file mode 100644 index 0000000..7530698 --- /dev/null +++ b/examples/analyse.rs @@ -0,0 +1,17 @@ +use bliss_rs::Song; +use std::env; + +/** + * Simple utility to print the result of an Analysis. + * + * Takes a list of files to analyse an the result of the corresponding Analysis. + */ +fn main() { + let args: Vec = env::args().skip(1).collect(); + for path in &args { + match Song::new(&path) { + Ok(song) => println!("{}: {:?}", path, song.analysis,), + Err(e) => println!("{}: {}", path, e), + } + } +} diff --git a/examples/distance.rs b/examples/distance.rs new file mode 100644 index 0000000..66f2c02 --- /dev/null +++ b/examples/distance.rs @@ -0,0 +1,26 @@ +use bliss_rs::Song; +use std::env; + +/** + * Simple utility to print distance between two songs according to bliss. + * + * Takes two file paths, and analyse the corresponding songs, printing + * the distance between the two files according to bliss. + */ +fn main() -> Result<(), String> { + let mut paths = env::args().skip(1).take(2); + + let first_path = paths.next().ok_or("Help: ./distance ")?; + let second_path = paths.next().ok_or("Help: ./distance ")?; + + let song1 = Song::new(&first_path).map_err(|x| x.to_string())?; + let song2 = Song::new(&second_path).map_err(|x| x.to_string())?; + + println!( + "d({}, {}) = {}", + song1.path, + song2.path, + song1.distance(&song2) + ); + Ok(()) +} diff --git a/src/chroma.rs b/src/chroma.rs new file mode 100644 index 0000000..1847b5b --- /dev/null +++ b/src/chroma.rs @@ -0,0 +1,638 @@ +//! Chroma feature extraction module. +//! +//! Contains functions to compute the chromagram of a song, and +//! then from this chromagram extract the song's tone and mode +//! (minor / major). +extern crate aubio_lib; +extern crate noisy_float; + +use crate::utils::stft; +use crate::utils::{hz_to_octs_inplace, Normalize}; +use crate::BlissError; +use ndarray::{arr1, arr2, concatenate, s, Array, Array1, Array2, Axis, Zip}; +use ndarray_stats::interpolate::Midpoint; +use ndarray_stats::QuantileExt; +use noisy_float::prelude::*; + +/** + * General object holding the chroma descriptor. + * + * Current chroma descriptors are interval features (see + * https://speech.di.uoa.gr/ICMC-SMC-2014/images/VOL_2/1461.pdf). + * + * Contrary to the other descriptors that can be used with streaming + * without consequences, this one performs better if the full song is used at + * once. + */ +pub(crate) struct ChromaDesc { + sample_rate: u32, + n_chroma: u32, + values_chroma: Array2, +} + +impl Normalize for ChromaDesc { + const MAX_VALUE: f32 = 0.12; + const MIN_VALUE: f32 = 0.; +} + +impl ChromaDesc { + pub const WINDOW_SIZE: usize = 8192; + + pub fn new(sample_rate: u32, n_chroma: u32) -> ChromaDesc { + ChromaDesc { + sample_rate, + n_chroma, + values_chroma: Array2::zeros((n_chroma as usize, 0)), + } + } + + /** + * Compute and store the chroma of a signal. + * + * Passing a full song here once instead of streaming smaller parts of the + * song will greatly improve accuracy. + */ + pub fn do_(&mut self, signal: &[f32]) -> Result<(), BlissError> { + let mut stft = stft(signal, ChromaDesc::WINDOW_SIZE, 2205); + let tuning = estimate_tuning( + self.sample_rate as u32, + &stft, + ChromaDesc::WINDOW_SIZE, + 0.01, + 12, + )?; + let chroma = chroma_stft( + self.sample_rate, + &mut stft, + ChromaDesc::WINDOW_SIZE, + self.n_chroma, + tuning, + )?; + self.values_chroma = concatenate![Axis(1), self.values_chroma, chroma]; + Ok(()) + } + + /** + * Get the song's interval features. + * + * Return the 6 pitch class set categories, as well as the major, minor, + * diminished and augmented triads. + * + * See this paper https://speech.di.uoa.gr/ICMC-SMC-2014/images/VOL_2/1461.pdf + * for more information ("Timbre-invariant Audio Features for Style Analysis of Classical + * Music"). + */ + pub fn get_values(&mut self) -> Vec { + chroma_interval_features(&self.values_chroma) + .mapv(|x| self.normalize(x as f32)) + .to_vec() + } +} + +// Functions below are Rust versions of python notebooks by AudioLabs Erlang +// (https://www.audiolabs-erlangen.de/resources/MIR/FMP/C0/C0.html) +fn chroma_interval_features(chroma: &Array2) -> Array1 { + let chroma = normalize_feature_sequence(&chroma.mapv(|x| (x * 15.).exp())); + let templates = arr2(&[ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 1, 1, 0], + [0, 0, 0, 1, 0, 0, 1, 0, 0, 1], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]); + let interval_feature_matrix = extract_interval_features(&chroma, &templates); + interval_feature_matrix.mean_axis(Axis(1)).unwrap() +} + +fn extract_interval_features(chroma: &Array2, templates: &Array2) -> Array2 { + let mut f_intervals: Array2 = Array::zeros((chroma.shape()[1], templates.shape()[1])); + for (template, mut f_interval) in templates + .axis_iter(Axis(1)) + .zip(f_intervals.axis_iter_mut(Axis(1))) + { + for shift in 0..12 { + let mut vec: Vec = template.to_vec(); + vec.rotate_right(shift); + let rolled = arr1(&vec); + let power = Zip::from(chroma.t()) + .and_broadcast(&rolled) + .map_collect(|&f, &s| f.powi(s)) + .map_axis_mut(Axis(1), |x| x.product()); + f_interval += &power; + } + } + f_intervals.t().to_owned() +} + +fn normalize_feature_sequence(feature: &Array2) -> Array2 { + let mut normalized_sequence = feature.to_owned(); + for mut column in normalized_sequence.columns_mut() { + let mut sum = column.mapv(|x| x.abs()).sum(); + if sum < 0.0001 { + sum = 1.; + } + column /= sum; + } + + normalized_sequence +} + +// All the functions below are more than heavily inspired from +// librosa"s code: https://github.com/librosa/librosa/blob/main/librosa/feature/spectral.py#L1165 +// chroma(22050, n_fft=5, n_chroma=12) +// +// Could be precomputed, but it takes very little time to compute it +// on the fly compared to the rest of the functions, and we'd lose the +// possibility to tweak parameters. +fn chroma_filter( + sample_rate: u32, + n_fft: usize, + n_chroma: u32, + tuning: f64, +) -> Result, BlissError> { + let ctroct = 5.0; + let octwidth = 2.; + let n_chroma_float = f64::from(n_chroma); + let n_chroma2 = (n_chroma_float / 2.0).round() as u32; + let n_chroma2_float = f64::from(n_chroma2); + + let frequencies = Array::linspace(0., f64::from(sample_rate), (n_fft + 1) as usize); + + let mut freq_bins = frequencies; + hz_to_octs_inplace(&mut freq_bins, tuning, n_chroma); + freq_bins.mapv_inplace(|x| x * n_chroma_float); + freq_bins[0] = freq_bins[1] - 1.5 * n_chroma_float; + + let mut binwidth_bins = Array::ones(freq_bins.raw_dim()); + binwidth_bins.slice_mut(s![0..freq_bins.len() - 1]).assign( + &(&freq_bins.slice(s![1..]) - &freq_bins.slice(s![..-1])).mapv(|x| { + if x <= 1. { + 1. + } else { + x + } + }), + ); + + let mut d: Array2 = Array::zeros((n_chroma as usize, (&freq_bins).len())); + for (idx, mut row) in d.rows_mut().into_iter().enumerate() { + row.fill(idx as f64); + } + d = -d + &freq_bins; + + d.mapv_inplace(|x| { + (x + n_chroma2_float + 10. * n_chroma_float) % n_chroma_float - n_chroma2_float + }); + d = d / binwidth_bins; + d.mapv_inplace(|x| (-0.5 * (2. * x) * (2. * x)).exp()); + + let mut wts = d; + // Normalize by computing the l2-norm over the columns + for mut col in wts.columns_mut() { + let mut sum = col.mapv(|x| x * x).sum().sqrt(); + if sum < f64::MIN_POSITIVE { + sum = 1.; + } + col /= sum; + } + + freq_bins.mapv_inplace(|x| (-0.5 * ((x / n_chroma_float - ctroct) / octwidth).powi(2)).exp()); + + wts *= &freq_bins; + + // np.roll(), np bro + let mut uninit: Vec = Vec::with_capacity((&wts).len()); + unsafe { + uninit.set_len(wts.len()); + } + let mut b = Array::from(uninit) + .into_shape(wts.dim()) + .map_err(|e| BlissError::AnalysisError(format!("in chroma: {}", e.to_string())))?; + b.slice_mut(s![-3.., ..]).assign(&wts.slice(s![..3, ..])); + b.slice_mut(s![..-3, ..]).assign(&wts.slice(s![3.., ..])); + + wts = b; + let non_aliased = (1 + n_fft / 2) as usize; + Ok(wts.slice_move(s![.., ..non_aliased])) +} + +fn pip_track( + sample_rate: u32, + spectrum: &Array2, + n_fft: usize, +) -> Result<(Vec, Vec), BlissError> { + let sample_rate_float = f64::from(sample_rate); + let fmin = 150.0_f64; + let fmax = 4000.0_f64.min(sample_rate_float / 2.0); + let threshold = 0.1; + + let fft_freqs = Array::linspace(0., sample_rate_float / 2., 1 + n_fft / 2); + + let length = spectrum.len_of(Axis(0)); + + // TODO>1.0 Make this a bitvec when that won't mean depending on a crate + let freq_mask = fft_freqs + .iter() + .map(|&f| (fmin <= f) && (f < fmax)) + .collect::>(); + + let ref_value = spectrum.map_axis(Axis(0), |x| { + let first: f64 = *x.first().expect("empty spectrum axis"); + let max = x.fold(first, |acc, &elem| if acc > elem { acc } else { elem }); + threshold * max + }); + + // There will be at most taken_columns * length elements in pitches / mags + let taken_columns = freq_mask + .iter() + .fold(0, |acc, &x| if x { acc + 1 } else { acc }); + let mut pitches = Vec::with_capacity(taken_columns * length); + let mut mags = Vec::with_capacity(taken_columns * length); + + let beginning = freq_mask + .iter() + .position(|&b| b) + .ok_or(BlissError::AnalysisError("in chroma".to_string()))?; + let end = freq_mask + .iter() + .rposition(|&b| b) + .ok_or(BlissError::AnalysisError("in chroma".to_string()))?; + + let zipped = Zip::indexed(spectrum.slice(s![beginning..end - 3, ..])) + .and(spectrum.slice(s![beginning + 1..end - 2, ..])) + .and(spectrum.slice(s![beginning + 2..end - 1, ..])); + + // No need to handle the last column, since freq_mask[length - 1] is + // always going to be `false` for 22.5kHz + zipped.for_each(|(i, j), &before_elem, &elem, &after_elem| { + if elem > ref_value[j] && after_elem <= elem && before_elem < elem { + let avg = 0.5 * (after_elem - before_elem); + let mut shift = 2. * elem - after_elem - before_elem; + if shift.abs() < f64::MIN_POSITIVE { + shift += 1.; + } + shift = avg / shift; + pitches.push(((i + beginning + 1) as f64 + shift) * sample_rate_float / n_fft as f64); + mags.push(elem + 0.5 * avg * shift); + } + }); + + Ok((pitches, mags)) +} + +// Only use this with strictly positive `frequencies`. +fn pitch_tuning( + frequencies: &mut Array1, + resolution: f64, + bins_per_octave: u32, +) -> Result { + if frequencies.is_empty() { + return Ok(0.0); + } + hz_to_octs_inplace(frequencies, 0.0, 12); + frequencies.mapv_inplace(|x| f64::from(bins_per_octave) * x % 1.0); + + // Put everything between -0.5 and 0.5. + frequencies.mapv_inplace(|x| if x >= 0.5 { x - 1. } else { x }); + + let indexes = ((frequencies.to_owned() - -0.5) / resolution).mapv(|x| x as usize); + let mut counts: Array1 = Array::zeros(((0.5 - -0.5) / resolution) as usize); + for &idx in indexes.iter() { + counts[idx] += 1; + } + let max_index = counts + .argmax() + .map_err(|e| BlissError::AnalysisError(format!("in chroma: {}", e.to_string())))?; + + // Return the bin with the most reoccuring frequency. + Ok((-50. + (100. * resolution * max_index as f64)) / 100.) +} + +fn estimate_tuning( + sample_rate: u32, + spectrum: &Array2, + n_fft: usize, + resolution: f64, + bins_per_octave: u32, +) -> Result { + let (pitch, mag) = pip_track(sample_rate, &spectrum, n_fft)?; + + let (filtered_pitch, filtered_mag): (Vec, Vec) = pitch + .iter() + .zip(&mag) + .filter(|(&p, _)| p > 0.) + .map(|(x, y)| (n64(*x), n64(*y))) + .unzip(); + + let threshold: N64 = Array::from(filtered_mag.to_vec()) + .quantile_axis_mut(Axis(0), n64(0.5), &Midpoint) + .map_err(|e| BlissError::AnalysisError(format!("in chroma: {}", e.to_string())))? + .into_scalar(); + + let mut pitch = filtered_pitch + .iter() + .zip(&filtered_mag) + .filter_map(|(&p, &m)| if m >= threshold { Some(p.into()) } else { None }) + .collect::>(); + pitch_tuning(&mut pitch, resolution, bins_per_octave) +} + +fn chroma_stft( + sample_rate: u32, + spectrum: &mut Array2, + n_fft: usize, + n_chroma: u32, + tuning: f64, +) -> Result, BlissError> { + spectrum.par_mapv_inplace(|x| x * x); + let mut raw_chroma = chroma_filter(sample_rate, n_fft, n_chroma, tuning)?; + + raw_chroma = raw_chroma.dot(spectrum); + for mut row in raw_chroma.columns_mut() { + let mut sum = row.mapv(|x| x.abs()).sum(); + if sum < f64::MIN_POSITIVE { + sum = 1.; + } + row /= sum; + } + Ok(raw_chroma) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::utils::stft; + use crate::{Song, SAMPLE_RATE}; + use ndarray::{arr1, arr2, Array2}; + use ndarray_npy::ReadNpyExt; + use std::fs::File; + + #[test] + fn test_chroma_interval_features() { + let file = File::open("data/chroma.npy").unwrap(); + let chroma = Array2::::read_npy(file).unwrap(); + let features = chroma_interval_features(&chroma); + let expected_features = arr1(&[ + 0.03860284, 0.02185281, 0.04224379, 0.06385278, 0.07311148, 0.02512566, 0.00319899, + 0.00311308, 0.00107433, 0.00241861, + ]); + for (expected, actual) in expected_features.iter().zip(&features) { + assert!(0.00000001 > (expected - actual.abs())); + } + } + + #[test] + fn test_extract_interval_features() { + let file = File::open("data/chroma-interval.npy").unwrap(); + let chroma = Array2::::read_npy(file).unwrap(); + let templates = arr2(&[ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 1, 1, 0], + [0, 0, 0, 1, 0, 0, 1, 0, 0, 1], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]); + + let file = File::open("data/interval-feature-matrix.npy").unwrap(); + let expected_interval_features = Array2::::read_npy(file).unwrap(); + + let interval_features = extract_interval_features(&chroma, &templates); + for (expected, actual) in expected_interval_features + .iter() + .zip(interval_features.iter()) + { + assert!(0.0000001 > (expected - actual).abs()); + } + } + + #[test] + fn test_normalize_feature_sequence() { + let array = arr2(&[[0.1, 0.3, 0.4], [1.1, 0.53, 1.01]]); + let expected_array = arr2(&[ + [0.08333333, 0.36144578, 0.28368794], + [0.91666667, 0.63855422, 0.71631206], + ]); + + let normalized_array = normalize_feature_sequence(&array); + + assert!(!array.is_empty() && !expected_array.is_empty()); + + for (expected, actual) in normalized_array.iter().zip(expected_array.iter()) { + assert!(0.0000001 > (expected - actual).abs()); + } + } + + #[test] + fn test_chroma_desc() { + let song = Song::decode("data/s16_mono_22_5kHz.flac").unwrap(); + let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12); + chroma_desc.do_(&song.sample_array).unwrap(); + let expected_values = vec![ + -0.35661936, + -0.63578653, + -0.29593682, + 0.06421304, + 0.21852458, + -0.581239, + -0.9466835, + -0.9481153, + -0.9820945, + -0.95968974, + ]; + for (expected, actual) in expected_values.iter().zip(chroma_desc.get_values().iter()) { + assert!(0.0000001 > (expected - actual).abs()); + } + } + + #[test] + fn test_chroma_stft_decode() { + let signal = Song::decode("data/s16_mono_22_5kHz.flac") + .unwrap() + .sample_array; + let mut stft = stft(&signal, 8192, 2205); + + let file = File::open("data/chroma.npy").unwrap(); + let expected_chroma = Array2::::read_npy(file).unwrap(); + + let chroma = chroma_stft(22050, &mut stft, 8192, 12, -0.04999999999999999).unwrap(); + + assert!(!chroma.is_empty() && !expected_chroma.is_empty()); + + for (expected, actual) in expected_chroma.iter().zip(chroma.iter()) { + assert!(0.0000001 > (expected - actual).abs()); + } + } + + #[test] + fn test_estimate_tuning() { + let file = File::open("data/spectrum-chroma.npy").unwrap(); + let arr = Array2::::read_npy(file).unwrap(); + + let tuning = estimate_tuning(22050, &arr, 2048, 0.01, 12).unwrap(); + assert!(0.000001 > (-0.09999999999999998 - tuning).abs()); + } + + #[test] + fn test_estimate_tuning_decode() { + let signal = Song::decode("data/s16_mono_22_5kHz.flac") + .unwrap() + .sample_array; + let stft = stft(&signal, 8192, 2205); + + let tuning = estimate_tuning(22050, &stft, 8192, 0.01, 12).unwrap(); + assert!(0.000001 > (-0.04999999999999999 - tuning).abs()); + } + + #[test] + fn test_pitch_tuning() { + let file = File::open("data/pitch-tuning.npy").unwrap(); + let mut pitch = Array1::::read_npy(file).unwrap(); + + assert_eq!(-0.1, pitch_tuning(&mut pitch, 0.05, 12).unwrap()); + } + + #[test] + fn test_pitch_tuning_no_frequencies() { + let mut frequencies = arr1(&[]); + assert_eq!(0.0, pitch_tuning(&mut frequencies, 0.05, 12).unwrap()); + } + + #[test] + fn test_pip_track() { + let file = File::open("data/spectrum-chroma.npy").unwrap(); + let spectrum = Array2::::read_npy(file).unwrap(); + + let mags_file = File::open("data/spectrum-chroma-mags.npy").unwrap(); + let expected_mags = Array1::::read_npy(mags_file).unwrap(); + + let pitches_file = File::open("data/spectrum-chroma-pitches.npy").unwrap(); + let expected_pitches = Array1::::read_npy(pitches_file).unwrap(); + + let (mut pitches, mut mags) = pip_track(22050, &spectrum, 2048).unwrap(); + pitches.sort_by(|a, b| a.partial_cmp(b).unwrap()); + mags.sort_by(|a, b| a.partial_cmp(b).unwrap()); + + for (expected_pitches, actual_pitches) in expected_pitches.iter().zip(pitches.iter()) { + assert!(0.00000001 > (expected_pitches - actual_pitches).abs()); + } + for (expected_mags, actual_mags) in expected_mags.iter().zip(mags.iter()) { + assert!(0.00000001 > (expected_mags - actual_mags).abs()); + } + } + + #[test] + fn test_chroma_filter() { + let file = File::open("data/chroma-filter.npy").unwrap(); + let expected_filter = Array2::::read_npy(file).unwrap(); + + let filter = chroma_filter(22050, 2048, 12, -0.1).unwrap(); + + for (expected, actual) in expected_filter.iter().zip(filter.iter()) { + assert!(0.000000001 > (expected - actual).abs()); + } + } +} + +#[cfg(all(feature = "bench", test))] +mod bench { + extern crate test; + use super::*; + use crate::utils::stft; + use crate::{Song, SAMPLE_RATE}; + use ndarray::{arr2, Array1, Array2}; + use ndarray_npy::ReadNpyExt; + use std::fs::File; + use test::Bencher; + + #[bench] + fn bench_estimate_tuning(b: &mut Bencher) { + let file = File::open("data/spectrum-chroma.npy").unwrap(); + let arr = Array2::::read_npy(file).unwrap(); + + b.iter(|| { + estimate_tuning(22050, &arr, 2048, 0.01, 12).unwrap(); + }); + } + + #[bench] + fn bench_pitch_tuning(b: &mut Bencher) { + let file = File::open("data/pitch-tuning.npy").unwrap(); + let pitch = Array1::::read_npy(file).unwrap(); + b.iter(|| { + pitch_tuning(&mut pitch.to_owned(), 0.05, 12).unwrap(); + }); + } + + #[bench] + fn bench_pip_track(b: &mut Bencher) { + let file = File::open("data/spectrum-chroma.npy").unwrap(); + let spectrum = Array2::::read_npy(file).unwrap(); + + b.iter(|| { + pip_track(22050, &spectrum, 2048).unwrap(); + }); + } + + #[bench] + fn bench_chroma_filter(b: &mut Bencher) { + b.iter(|| { + chroma_filter(22050, 2048, 12, -0.1).unwrap(); + }); + } + + #[bench] + fn bench_normalize_feature_sequence(b: &mut Bencher) { + let array = arr2(&[[0.1, 0.3, 0.4], [1.1, 0.53, 1.01]]); + b.iter(|| { + normalize_feature_sequence(&array); + }); + } + + #[bench] + fn bench_chroma_desc(b: &mut Bencher) { + let song = Song::decode("data/s16_mono_22_5kHz.flac").unwrap(); + let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12); + let signal = song.sample_array; + b.iter(|| { + chroma_desc.do_(&signal).unwrap(); + chroma_desc.get_values(); + }); + } + + #[bench] + fn bench_chroma_stft(b: &mut Bencher) { + let song = Song::decode("data/s16_mono_22_5kHz.flac").unwrap(); + let mut chroma_desc = ChromaDesc::new(SAMPLE_RATE, 12); + let signal = song.sample_array; + b.iter(|| { + chroma_desc.do_(&signal).unwrap(); + chroma_desc.get_values(); + }); + } + + #[bench] + fn bench_chroma_stft_decode(b: &mut Bencher) { + let signal = Song::decode("data/s16_mono_22_5kHz.flac") + .unwrap() + .sample_array; + let mut stft = stft(&signal, 8192, 2205); + + b.iter(|| { + chroma_stft(22050, &mut stft, 8192, 12, -0.04999999999999999).unwrap(); + }); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7375ea6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,131 @@ +#![cfg_attr(feature = "bench", feature(test))] +mod chroma; +pub mod library; +mod misc; +mod song; +mod temporal; +mod timbral; +mod utils; + +extern crate crossbeam; +extern crate num_cpus; +use thiserror::Error; + +const CHANNELS: u16 = 1; +const SAMPLE_RATE: u32 = 22050; + +#[derive(Default, Debug, PartialEq, Clone)] +/// Simple object used to represent a Song, with its path, analysis, and +/// other metadata (artist, genre...) +pub struct Song { + pub path: String, + pub artist: String, + pub title: String, + pub album: String, + pub track_number: String, + pub genre: String, + /// Vec containing analysis, in order: tempo, zero-crossing rate, + /// mean spectral centroid, std deviation spectral centroid, + /// mean spectral rolloff, std deviation spectral rolloff + /// mean spectral_flatness, std deviation spectral flatness, + /// mean loudness, std deviation loudness + /// chroma interval feature 1 to 10 + pub analysis: Vec, +} + +#[derive(Error, Debug, PartialEq)] +pub enum BlissError { + #[error("Error happened while decoding file – {0}")] + DecodingError(String), + #[error("Error happened while analyzing file – {0}")] + AnalysisError(String), + #[error("Error happened with the music library provider - {0}")] + ProviderError(String), +} + +/// Simple function to bulk analyze a set of songs represented by their +/// absolute paths. +/// +/// When making an extension for an audio player, prefer +/// implementing the `Library` trait. +#[doc(hidden)] +pub fn bulk_analyse(paths: Vec) -> Vec> { + let mut songs = Vec::with_capacity(paths.len()); + let num_cpus = num_cpus::get(); + + crossbeam::scope(|s| { + let mut handles = Vec::with_capacity(paths.len() / num_cpus); + let mut chunk_number = paths.len() / num_cpus; + if chunk_number == 0 { + chunk_number = paths.len(); + } + for chunk in paths.chunks(chunk_number) { + handles.push(s.spawn(move |_| { + let mut result = Vec::with_capacity(chunk.len()); + for path in chunk { + let song = Song::new(&path); + result.push(song); + } + result + })); + } + + for handle in handles { + songs.extend(handle.join().unwrap()); + } + }) + .unwrap(); + + songs +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bulk_analyse() { + let results = bulk_analyse(vec![ + String::from("data/s16_mono_22_5kHz.flac"), + String::from("data/s16_mono_22_5kHz.flac"), + String::from("nonexistent"), + String::from("data/s16_stereo_22_5kHz.flac"), + String::from("nonexistent"), + String::from("nonexistent"), + String::from("nonexistent"), + String::from("nonexistent"), + String::from("nonexistent"), + String::from("nonexistent"), + String::from("nonexistent"), + ]); + let mut errored_songs: Vec = results + .iter() + .filter_map(|x| x.as_ref().err().map(|x| x.to_string())) + .collect(); + errored_songs.sort_by(|a, b| a.cmp(b)); + + let mut analysed_songs: Vec = results + .iter() + .filter_map(|x| x.as_ref().ok().map(|x| x.path.to_string())) + .collect(); + analysed_songs.sort_by(|a, b| a.cmp(b)); + + assert_eq!( + vec![ + String::from( + "Error happened while decoding file – while opening format: ffmpeg::Error(2: No such file or directory)." + ); + 8 + ], + errored_songs + ); + assert_eq!( + vec![ + String::from("data/s16_mono_22_5kHz.flac"), + String::from("data/s16_mono_22_5kHz.flac"), + String::from("data/s16_stereo_22_5kHz.flac"), + ], + analysed_songs, + ); + } +} diff --git a/src/library.rs b/src/library.rs new file mode 100644 index 0000000..0b6f9ad --- /dev/null +++ b/src/library.rs @@ -0,0 +1,389 @@ +//! Module containing the Library trait, useful to get started to implement +//! a plug-in for an audio player. +use crate::{BlissError, Song}; +use log::{error, info, warn}; +use ndarray::{arr1, Array}; +use noisy_float::prelude::*; +use std::sync::mpsc; +use std::sync::mpsc::{Receiver, Sender}; +use std::thread; + +/// Library trait to make creating plug-ins for existing audio players easier. +pub trait Library { + /// Return the absolute path of all the songs in an + /// audio player's music library. + fn get_songs_paths(&self) -> Result, BlissError>; + /// Store an analyzed Song object in some (cold) storage, e.g. + /// a database, a file... + fn store_song(&mut self, song: &Song) -> Result<(), BlissError>; + /// Log and / or store that an error happened while trying to decode and + /// analyze a song. + fn store_error_song(&mut self, song_path: String, error: BlissError) -> Result<(), BlissError>; + /// Retrieve a list of all the stored Songs. + /// + /// This should work only after having run `analyze_library` at least + /// once. + fn get_stored_songs(&self) -> Result, BlissError>; + + /// Return a list of songs that are similar to ``first_song``. + /// + /// # Arguments + /// + /// * `first_song` - The song the playlist will be built from. + /// * `playlist_length` - The playlist length. If there are not enough + /// songs in the library, it will be truncated to the size of the library. + /// + /// # Returns + /// + /// A vector of `playlist_length` Songs, including `first_song`, that you + /// most likely want to plug in your audio player by using something like + /// `ret.map(|song| song.path.to_owned()).collect::>()`. + fn playlist_from_song( + &self, + first_song: Song, + playlist_length: usize, + ) -> Result, BlissError> { + let analysis_current_song = arr1(&first_song.analysis.to_vec()); + let mut songs = self.get_stored_songs()?; + let m = Array::eye(first_song.analysis.len()); + songs.sort_by_cached_key(|song| { + n32((arr1(&song.analysis) - &analysis_current_song) + .dot(&m) + .dot(&(arr1(&song.analysis) - &analysis_current_song))) + }); + Ok(songs + .into_iter() + .take(playlist_length) + .collect::>()) + } + + /// Analyze and store songs in `paths`, using `store_song` and + /// `store_error_song` implementations. + /// + /// Note: this is mostly useful for updating a song library. For the first + /// run, you probably want to use `analyze_library`. + fn analyze_paths(&mut self, paths: Vec) -> Result<(), BlissError> { + if paths.is_empty() { + return Ok(()); + } + let num_cpus = num_cpus::get(); + + let (tx, rx): ( + Sender<(String, Result)>, + Receiver<(String, Result)>, + ) = mpsc::channel(); + let mut handles = Vec::new(); + let mut chunk_length = paths.len() / num_cpus; + if chunk_length == 0 { + chunk_length = paths.len(); + } + + for chunk in paths.chunks(chunk_length) { + let tx_thread = tx.clone(); + let owned_chunk = chunk.to_owned(); + let child = thread::spawn(move || { + for path in owned_chunk { + info!("Analyzing file '{}'", path); + let song = Song::new(&path); + tx_thread.send((path.to_string(), song)).unwrap(); + } + drop(tx_thread); + }); + handles.push(child); + } + drop(tx); + + for (path, song) in rx.iter() { + // A storage fail should just warn the user, but not abort the whole process + match song { + Ok(song) => { + self.store_song(&song) + .unwrap_or_else(|_| error!("Error while storing song '{}'", (&song).path)); + info!("Analyzed and stored song '{}' successfully.", (&song).path) + } + Err(e) => { + self.store_error_song(path.to_string(), e) + .unwrap_or_else(|e| { + error!("Error while storing errored song '{}': {}", path, e) + }); + warn!("Analysis of song '{}' failed. Storing error.", path) + } + } + } + + for child in handles { + child + .join() + .map_err(|_| BlissError::AnalysisError(format!("in analysis")))?; + } + Ok(()) + } + + /// Analyzes a song library, using `get_songs_paths`, `store_song` and + /// `store_error_song` implementations. + fn analyze_library(&mut self) -> Result<(), BlissError> { + let paths = self + .get_songs_paths() + .map_err(|e| BlissError::ProviderError(e.to_string()))?; + self.analyze_paths(paths)?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[derive(Default)] + struct TestLibrary { + internal_storage: Vec, + failed_files: Vec<(String, String)>, + } + + impl Library for TestLibrary { + fn get_songs_paths(&self) -> Result, BlissError> { + Ok(vec![ + String::from("./data/white_noise.flac"), + String::from("./data/s16_mono_22_5kHz.flac"), + String::from("not-existing.foo"), + String::from("definitely-not-existing.foo"), + ]) + } + + fn store_song(&mut self, song: &Song) -> Result<(), BlissError> { + self.internal_storage.push(song.to_owned()); + Ok(()) + } + + fn store_error_song( + &mut self, + song_path: String, + error: BlissError, + ) -> Result<(), BlissError> { + self.failed_files.push((song_path, error.to_string())); + Ok(()) + } + + fn get_stored_songs(&self) -> Result, BlissError> { + Ok(self.internal_storage.to_owned()) + } + } + + #[derive(Default)] + struct FailingLibrary; + + impl Library for FailingLibrary { + fn get_songs_paths(&self) -> Result, BlissError> { + Err(BlissError::ProviderError(String::from( + "Could not get songs path", + ))) + } + + fn store_song(&mut self, _: &Song) -> Result<(), BlissError> { + Ok(()) + } + + fn get_stored_songs(&self) -> Result, BlissError> { + Err(BlissError::ProviderError(String::from( + "Could not get stored songs", + ))) + } + + fn store_error_song(&mut self, _: String, _: BlissError) -> Result<(), BlissError> { + Ok(()) + } + } + + #[derive(Default)] + struct FailingStorage; + + impl Library for FailingStorage { + fn get_songs_paths(&self) -> Result, BlissError> { + Ok(vec![ + String::from("./data/white_noise.flac"), + String::from("./data/s16_mono_22_5kHz.flac"), + String::from("not-existing.foo"), + String::from("definitely-not-existing.foo"), + ]) + } + + fn store_song(&mut self, song: &Song) -> Result<(), BlissError> { + Err(BlissError::ProviderError(format!( + "Could not store song {}", + song.path + ))) + } + + fn get_stored_songs(&self) -> Result, BlissError> { + Ok(vec![]) + } + + fn store_error_song( + &mut self, + song_path: String, + error: BlissError, + ) -> Result<(), BlissError> { + Err(BlissError::ProviderError(format!( + "Could not store errored song: {}, with error: {}", + song_path, error + ))) + } + } + + #[test] + fn test_analyze_library_fail() { + let mut test_library = FailingLibrary {}; + assert_eq!( + test_library.analyze_library(), + Err(BlissError::ProviderError(String::from( + "Error happened with the music library provider - Could not get songs path" + ))), + ); + } + + #[test] + fn test_playlist_from_song_fail() { + let test_library = FailingLibrary {}; + let song = Song { + path: String::from("path-to-first"), + analysis: vec![0., 0., 0.], + ..Default::default() + }; + + assert_eq!( + test_library.playlist_from_song(song, 10), + Err(BlissError::ProviderError(String::from( + "Could not get stored songs" + ))), + ); + } + + #[test] + fn test_analyze_library_fail_storage() { + let mut test_library = FailingStorage {}; + + // A storage fail should just warn the user, but not abort the whole process + assert!(test_library.analyze_library().is_ok()) + } + + #[test] + fn test_analyze_library() { + let mut test_library = TestLibrary { + internal_storage: vec![], + failed_files: vec![], + }; + test_library.analyze_library().unwrap(); + + let mut failed_files = test_library + .failed_files + .iter() + .map(|x| x.0.to_owned()) + .collect::>(); + failed_files.sort(); + + assert_eq!( + failed_files, + vec![ + String::from("definitely-not-existing.foo"), + String::from("not-existing.foo"), + ], + ); + + let mut songs = test_library + .internal_storage + .iter() + .map(|x| x.path.to_owned()) + .collect::>(); + songs.sort(); + + assert_eq!( + songs, + vec![ + String::from("./data/s16_mono_22_5kHz.flac"), + String::from("./data/white_noise.flac"), + ], + ); + + test_library + .internal_storage + .iter() + .for_each(|x| assert!(x.analysis.len() > 0)); + } + + #[test] + fn test_playlist_from_song() { + let mut test_library = TestLibrary::default(); + let first_song = Song { + path: String::from("path-to-first"), + analysis: vec![0., 0., 0.], + ..Default::default() + }; + + let second_song = Song { + path: String::from("path-to-second"), + analysis: vec![0.1, 0., 0.], + ..Default::default() + }; + + let third_song = Song { + path: String::from("path-to-third"), + analysis: vec![10., 11., 10.], + ..Default::default() + }; + + let fourth_song = Song { + path: String::from("path-to-fourth"), + analysis: vec![20., 21., 20.], + ..Default::default() + }; + + test_library.internal_storage = vec![ + first_song.to_owned(), + fourth_song.to_owned(), + third_song.to_owned(), + second_song.to_owned(), + ]; + assert_eq!( + vec![first_song.to_owned(), second_song, third_song], + test_library.playlist_from_song(first_song, 3).unwrap() + ); + } + + #[test] + fn test_playlist_from_song_too_little_songs() { + let mut test_library = TestLibrary::default(); + let first_song = Song { + path: String::from("path-to-first"), + analysis: vec![0., 0., 0.], + ..Default::default() + }; + + let second_song = Song { + path: String::from("path-to-second"), + analysis: vec![0.1, 0., 0.], + ..Default::default() + }; + + let third_song = Song { + path: String::from("path-to-third"), + analysis: vec![10., 11., 10.], + ..Default::default() + }; + + test_library.internal_storage = vec![ + first_song.to_owned(), + second_song.to_owned(), + third_song.to_owned(), + ]; + assert_eq!( + vec![first_song.to_owned(), second_song, third_song], + test_library.playlist_from_song(first_song, 200).unwrap() + ); + } + + #[test] + fn test_analyze_empty_path() { + let mut test_library = TestLibrary::default(); + assert!(test_library.analyze_paths(vec![]).is_ok()); + } +} diff --git a/src/misc.rs b/src/misc.rs new file mode 100644 index 0000000..b36b67e --- /dev/null +++ b/src/misc.rs @@ -0,0 +1,108 @@ +//! Miscellaneous feature extraction module. +//! +//! Contains various descriptors that don't fit in one of the +//! existing categories. +extern crate aubio_lib; + +use aubio_rs::level_lin; +use ndarray::{arr1, Axis}; + +use super::utils::{mean, Normalize}; + +/** + * Loudness (in dB) detection object. + * + * It indicates how "loud" a recording of a song is. For a given audio signal, + * this value increases if the amplitude of the signal, and nothing else, is + * increased. + * + * Of course, this makes this result dependent of the recording, meaning + * the same song would yield different loudness on different recordings. Which + * is exactly what we want, given that this is not a music theory project, but + * one that aims at giving the best real-life results. + * + * Ranges between -90 dB (~silence) and 0 dB. + * + * (This is technically the sound pressure level of the track, but loudness is + * way more visual) + */ +#[derive(Default)] +pub(crate) struct LoudnessDesc { + pub values: Vec, +} + +impl LoudnessDesc { + pub const WINDOW_SIZE: usize = 1024; + + pub fn do_(&mut self, chunk: &[f32]) { + let level = level_lin(chunk); + self.values.push(level); + } + + pub fn get_value(&mut self) -> Vec { + let mut std_value = arr1(&self.values).std_axis(Axis(0), 0.).into_scalar(); + let mut mean_value = mean(&self.values); + // Make sure the dB don't go less than -90dB + if mean_value < 1e-9 { + mean_value = 1e-9 + }; + if std_value < 1e-9 { + std_value = 1e-9 + } + vec![ + self.normalize(10.0 * mean_value.log10()), + self.normalize(10.0 * std_value.log10()), + ] + } +} + +impl Normalize for LoudnessDesc { + const MAX_VALUE: f32 = 0.; + const MIN_VALUE: f32 = -90.; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Song; + + #[test] + fn test_loudness() { + let song = Song::decode("data/s16_mono_22_5kHz.flac").unwrap(); + let mut loudness_desc = LoudnessDesc::default(); + for chunk in song.sample_array.chunks_exact(LoudnessDesc::WINDOW_SIZE) { + loudness_desc.do_(&chunk); + } + let expected_values = vec![0.271263, 0.2577181]; + for (expected, actual) in expected_values.iter().zip(loudness_desc.get_value().iter()) { + assert!(0.01 > (expected - actual).abs()); + } + } + + #[test] + fn test_loudness_boundaries() { + let mut loudness_desc = LoudnessDesc::default(); + let silence_chunk = vec![0.; 1024]; + loudness_desc.do_(&silence_chunk); + let expected_values = vec![-1., -1.]; + for (expected, actual) in expected_values.iter().zip(loudness_desc.get_value().iter()) { + assert!(0.0000001 > (expected - actual).abs()); + } + + let mut loudness_desc = LoudnessDesc::default(); + let silence_chunk = vec![1.; 1024]; + loudness_desc.do_(&silence_chunk); + let expected_values = vec![1., -1.]; + for (expected, actual) in expected_values.iter().zip(loudness_desc.get_value().iter()) { + assert!(0.0000001 > (expected - actual).abs()); + } + + let mut loudness_desc = LoudnessDesc::default(); + let silence_chunk = vec![-1.; 1024]; + loudness_desc.do_(&silence_chunk); + let expected_values = vec![1., -1.]; + for (expected, actual) in expected_values.iter().zip(loudness_desc.get_value().iter()) { + assert!(0.0000001 > (expected - actual).abs()); + } + } +} diff --git a/src/song.rs b/src/song.rs new file mode 100644 index 0000000..11f19d1 --- /dev/null +++ b/src/song.rs @@ -0,0 +1,606 @@ +//! Song decoding / analysis module. +//! +//! Use decoding, and features-extraction functions from other modules +//! e.g. tempo features, spectral features, etc to build a Song and its +//! corresponding Analysis. +extern crate aubio_lib; + +extern crate crossbeam; +extern crate ffmpeg_next as ffmpeg; +extern crate ndarray; +extern crate ndarray_npy; + +use super::CHANNELS; +use crate::chroma::ChromaDesc; +use crate::misc::LoudnessDesc; +use crate::temporal::BPMDesc; +use crate::timbral::{SpectralDesc, ZeroCrossingRateDesc}; +use crate::{BlissError, Song, SAMPLE_RATE}; +use ::log::warn; +use crossbeam::thread; +use ffmpeg_next::codec::threading::{Config, Type as ThreadingType}; +use ffmpeg_next::software::resampling::context::Context; +use ffmpeg_next::util; +use ffmpeg_next::util::channel_layout::ChannelLayout; +use ffmpeg_next::util::error::Error; +use ffmpeg_next::util::error::EINVAL; +use ffmpeg_next::util::format::sample::{Sample, Type}; +use ffmpeg_next::util::frame::audio::Audio; +use ffmpeg_next::util::log; +use ffmpeg_next::util::log::level::Level; +use ndarray::{arr1, Array}; +use std::sync::mpsc; +use std::sync::mpsc::Receiver; +use std::thread as std_thread; + +fn push_to_sample_array(frame: &ffmpeg::frame::Audio, sample_array: &mut Vec) { + if frame.samples() == 0 { + return; + } + // Account for the padding + let actual_size = util::format::sample::Buffer::size( + Sample::F32(Type::Packed), + CHANNELS, + frame.samples(), + false, + ); + let f32_frame: Vec = frame.data(0)[..actual_size] + .chunks_exact(4) + .map(|x| { + let mut a: [u8; 4] = [0; 4]; + a.copy_from_slice(x); + f32::from_le_bytes(a) + }) + .collect(); + sample_array.extend_from_slice(&f32_frame); +} + +#[derive(Default, Debug)] +pub(crate) struct InternalSong { + pub path: String, + pub artist: String, + pub title: String, + pub album: String, + pub track_number: String, + pub genre: String, + pub sample_array: Vec, +} + +fn resample_frame( + rx: Receiver