diff --git a/Cargo.lock b/Cargo.lock index 9573f43..82dffcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,14 +3,25 @@ version = 3 [[package]] -name = "atty" -version = "0.2.14" +name = "alsa" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44" dependencies = [ - "hermit-abi", + "alsa-sys", + "bitflags", "libc", - "winapi", + "nix 0.24.3", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", ] [[package]] @@ -19,6 +30,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bindgen" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a022e58a142a46fea340d68012b9201c094e93ec3d033a944a24f8fd4a4f09a" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -26,16 +57,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "byteorder" -version = "1.4.3" +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bytes" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -43,75 +98,198 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "ciborium" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" + +[[package]] +name = "ciborium-ll" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clang-sys" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" -version = "3.2.23" +version = "4.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" dependencies = [ - "atty", "bitflags", "clap_derive", "clap_lex", - "indexmap", + "is-terminal", "once_cell", "strsim", "termcolor", - "textwrap", ] [[package]] name = "clap_derive" -version = "3.2.18" +version = "4.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" dependencies = [ "heck", "proc-macro-error", - "proc-macro2 1.0.49", - "quote 1.0.23", - "syn 1.0.107", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" dependencies = [ "os_str_bytes", ] [[package]] -name = "colored" -version = "2.0.0" +name = "combine" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" dependencies = [ - "atty", - "lazy_static", - "winapi", + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "coreaudio-rs" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb17e2d1795b1996419648915df94bc7103c28f7b48062d7acf4652fc371b2ff" +dependencies = [ + "bitflags", + "core-foundation-sys 0.6.2", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9444b94b8024feecc29e01a9706c69c1e26bfee480221c90764200cfd778fb" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34fa7b20adf588f73f094cd9b1d944977c686e37a2759ea217ab174f017e10a" +dependencies = [ + "alsa", + "core-foundation-sys 0.8.3", + "coreaudio-rs", + "dasp_sample", + "jni 0.19.0", + "js-sys", + "libc", + "mach", + "ndk", + "ndk-context", + "oboe", + "once_cell", + "parking_lot", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows", ] [[package]] name = "ctrlc" -version = "3.2.4" +version = "3.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" +checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" dependencies = [ - "nix", + "nix 0.26.2", "windows-sys", ] [[package]] -name = "fastrand" -version = "1.8.0" +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ - "instant", + "cc", + "libc", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.12.3" @@ -120,18 +298,15 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "hound" @@ -140,48 +315,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1" [[package]] -name = "include_dir" -version = "0.7.3" +name = "indexmap" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ - "include_dir_macros", + "autocfg", + "hashbrown", ] [[package]] -name = "include_dir_macros" -version = "0.7.3" +name = "io-lifetimes" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" dependencies = [ - "proc-macro2 1.0.49", - "quote 1.0.23", + "libc", + "windows-sys", ] [[package]] -name = "indexmap" -version = "1.9.2" +name = "is-terminal" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" dependencies = [ - "autocfg", - "hashbrown", + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", ] [[package]] -name = "instant" -version = "0.1.12" +name = "jni" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" dependencies = [ - "cfg-if", + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", ] [[package]] -name = "itoa" -version = "1.0.5" +name = "js-sys" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] [[package]] name = "lazy_static" @@ -189,11 +404,17 @@ 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.139" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "libloading" @@ -205,6 +426,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.17" @@ -214,11 +451,72 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "nix" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", "cfg-if", @@ -227,24 +525,35 @@ dependencies = [ ] [[package]] -name = "nnnoiseless" -version = "0.5.1" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d377ce2fb579ed5c14cfa0d39e70849030fdf673d6d1a764cadb2dfbb02a50" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "once_cell", - "rustfft", + "memchr", + "minimal-lexical", ] [[package]] name = "num-complex" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -265,19 +574,54 @@ dependencies = [ ] [[package]] -name = "num_threads" -version = "0.1.6" +name = "num_enum" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ - "libc", + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "oboe" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8868cc237ee02e2d9618539a23a8d228b9bb3fc2e7a5b11eed3831de77c395d0" +dependencies = [ + "jni 0.20.0", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f44155e7fb718d3cfddcf70690b2b51ac4412f347cd9e4fbe511abe9cd7b5f2" +dependencies = [ + "cc", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "os_str_bytes" @@ -285,6 +629,41 @@ version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + [[package]] name = "primal-check" version = "0.3.3" @@ -294,6 +673,16 @@ dependencies = [ "num-integer", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -301,9 +690,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.49", - "quote 1.0.23", - "syn 1.0.107", + "proc-macro2", + "quote", + "syn", "version_check", ] @@ -313,56 +702,34 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.49", - "quote 1.0.23", + "proc-macro2", + "quote", "version_check", ] [[package]] name = "proc-macro2" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "proc-macro2" -version = "1.0.49" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" dependencies = [ "unicode-ident", ] -[[package]] -name = "pv_recorder" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32150b769ae414f304515c8ce924c6753638c4eaea48bceecbcd85f69d2d16cc" -dependencies = [ - "libc", - "libloading", -] - [[package]] name = "quote" -version = "0.6.13" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ - "proc-macro2 0.4.30", + "proc-macro2", ] [[package]] -name = "quote" -version = "1.0.23" +name = "raw-window-handle" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" -dependencies = [ - "proc-macro2 1.0.49", -] +checksum = "4f851a03551ceefd30132e447f07f96cb7011d6b658374f3aed847333adb5559" [[package]] name = "realfft" @@ -383,19 +750,25 @@ dependencies = [ ] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ - "winapi", + "regex-syntax", ] +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + [[package]] name = "rubato" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ceb88cff5e2414abd59f1470673e1bba0627118dfc13ce33cebf67ea4a8acb0" +checksum = "cd70209c27d5b08f5528bdc779ea3ffb418954e28987f9f9775c6eac41003f9c" dependencies = [ "num-complex", "num-integer", @@ -404,13 +777,10 @@ dependencies = [ ] [[package]] -name = "rustc_version" -version = "0.2.3" +name = "rustc-hash" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustfft" @@ -427,97 +797,91 @@ dependencies = [ "version_check", ] +[[package]] +name = "rustix" +version = "0.36.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustpotter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f96bd17b421c223dad9966b4a97a72216e9ca6d42c5596f75f6a16de1bc2f5" +checksum = "e6a88d0514dd5dcc988c78f61be4f60fdb37d4872e23c7d8d6d2d3ea23655f97" dependencies = [ + "ciborium", "hound", - "log", - "nnnoiseless", "rubato", - "savefile", - "savefile-derive", - "simple-matrix", - "webrtc-vad", + "rustfft", + "serde", ] [[package]] name = "rustpotter-cli" -version = "1.0.1" +version = "2.0.0" dependencies = [ "clap", + "cpal", "ctrlc", "hound", - "include_dir", - "log", - "pv_recorder", "rustpotter", - "simple_logger", - "tempfile", + "time", ] [[package]] -name = "savefile" -version = "0.10.1" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33022731817bb74a2e27487c0caa94d040d5a87ab010624c56d340ceef464f9" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "byteorder", - "rustc_version", + "winapi-util", ] [[package]] -name = "savefile-derive" -version = "0.10.1" +name = "scopeguard" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de83311908b9b76a3efa305abbcd133d3eacaeff9da42d2f916b40ae71a7083b" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.14.9", -] +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "semver" -version = "0.9.0" +name = "serde" +version = "1.0.156" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4" dependencies = [ - "semver-parser", + "serde_derive", ] [[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.151" +name = "serde_derive" +version = "1.0.156" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0" +checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "simple-matrix" -version = "0.1.2" +name = "shlex" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6927a3d2ea606d10c38c7b923e108e29a2f47ca287e1cb707fa898ce4d58aa7c" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] -name = "simple_logger" -version = "2.3.0" +name = "smallvec" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48047e77b528151aaf841a10a9025f9459da80ba820e425ff7eb005708a76dc7" -dependencies = [ - "atty", - "colored", - "log", - "time", - "winapi", -] +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "static_assertions" @@ -539,67 +903,52 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "0.14.9" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "syn" -version = "1.0.107" +name = "termcolor" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ - "proc-macro2 1.0.49", - "quote 1.0.23", - "unicode-ident", + "winapi-util", ] [[package]] -name = "tempfile" -version = "3.3.0" +name = "thiserror" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "thiserror-impl", ] [[package]] -name = "termcolor" -version = "1.1.3" +name = "thiserror-impl" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ - "winapi-util", + "proc-macro2", + "quote", + "syn", ] -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "time" -version = "0.3.17" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ - "itoa", - "libc", - "num_threads", "serde", "time-core", - "time-macros", ] [[package]] @@ -609,12 +958,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] -name = "time-macros" -version = "0.2.6" +name = "toml_datetime" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" + +[[package]] +name = "toml_edit" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08de71aa0d6e348f070457f85af8bd566e2bc452156a423ddf22861b3a953fae" dependencies = [ - "time-core", + "indexmap", + "toml_datetime", + "winnow", ] [[package]] @@ -629,15 +986,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" - -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "version_check" @@ -646,12 +997,90 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "webrtc-vad" -version = "0.4.0" +name = "walkdir" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a1e40fd6ca90be95459152a2537f2ba4286ee1b13073f7ebcaa74fc94e3008" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ - "cc", + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", ] [[package]] @@ -685,11 +1114,29 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -702,42 +1149,51 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "winnow" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index cbbfb5f..fecf94a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,16 @@ [package] name = "rustpotter-cli" -version = "1.0.1" +version = "2.0.0" edition = "2021" license = "Apache-2.0" -description = "CLI for Rustpotter, an open source wake word spotter forged in rust." +description = "CLI for Rustpotter, an open source wakeword spotter forged in rust." authors = ["Miguel Álvarez Díez "] repository = "https://github.com/GiviMAD/rustpotter" - +exclude = ["tools/**",".github",".gitignore"] [dependencies] -rustpotter = { version = "1.0.1", features = ["files", "build", "log", "vad"] } -log = "0.4.6" -pv_recorder = "1.0.2" +rustpotter = { version = "2.0.0" } ctrlc = "3.2.2" -clap = { version = "3.1.13", features = ["derive"] } +clap = { version = "4.1.6", features = ["derive"] } hound = "3.4.0" -include_dir = "0.7.2" -tempfile = "3.3.0" -simple_logger = "2.1.0" - -[features] -default = [] -# include recorder library into the binary for distribution outside cargo -dist = [] \ No newline at end of file +cpal = "0.15.0" +time = "0.3.20" diff --git a/README.md b/README.md index 004404e..35e6e97 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Rustpotter CLI -## CLI for Rustpotter, an open source wake word spotter forged in rust +## CLI for Rustpotter, an open source wakeword spotter forged in rust
@@ -13,32 +13,32 @@ This is a client for using the [rustpotter](https://github.com/GiviMAD/rustpotte ## Use Example ```sh -# quick look up to the help +# print help rustpotter-cli -h -# list available input devices +# print command help +rustpotter-cli spot -h +# list available input devices and configs rustpotter-cli devices # record samples, you should press "ctrl + c" to stop after saying your wakeword rustpotter-cli record hey_home.wav rustpotter-cli record hey_home1.wav rustpotter-cli record hey_home2.wav -rustpotter-cli record hey_home3.wav # check that your samples are correctly trimmed and without noise using any player ... # build a model, this op is idempotent (same input samples with same options = same model) rustpotter-cli build-model \ --model-path hey_home.rpw \ --model-name "hey home" \ -hey_home.wav hey_home1.wav hey_home2.wav hey_home3.wav -# test the model accuracy over the samples -rustpotter-cli test-model hey_home.rpw hey_home.wav -rustpotter-cli test-model hey_home.rpw hey_home1.wav -rustpotter-cli test-model hey_home.rpw hey_home2.wav -rustpotter-cli test-model hey_home.rpw hey_home3.wav +hey_home.wav hey_home1.wav hey_home2.wav +# test the model accuracy over the samples in verbose mode to print partial detections +rustpotter-cli test-model -v hey_home.rpw hey_home.wav +rustpotter-cli test-model -v hey_home.rpw hey_home1.wav +rustpotter-cli test-model -v hey_home.rpw hey_home2.wav # test the spot functionality in real time, customizing # the default detection threshold rustpotter-cli spot -t 0.563 hey_home.rpw # rebuild a model adding a custom threshold for the word, -# this one has prevalence over the default one +# this one has prevalence over the spot configuration rustpotter-cli build-model \ --averaged-threshold 0.54 \ --threshold 0.54 \ @@ -48,5 +48,6 @@ hey_home.wav hey_home1.wav hey_home2.wav hey_home3.wav # you can spot using multiple models rustpotter-cli spot hey_home.rpw good_morning.rpw ... # you will get an output like this in your terminal on each spot event -Detected 'good morning' with score 0.6146946! +Wakeword detection: 10:58:53 +RustpotterDetection { name: "hey home", avg_score: 0.37827095, score: 0.5000453, scores: {"hey_home2.wav": 0.43628272, "hey_home.wav": 0.5000453, "hey_home1.wav": 0.4230849}, counter: 7 } ``` diff --git a/build.rs b/build.rs deleted file mode 100644 index 2448527..0000000 --- a/build.rs +++ /dev/null @@ -1,144 +0,0 @@ -#[cfg(feature = "dist")] -use std::{env, fs, path::PathBuf}; - -fn main() { - #[cfg(feature = "dist")] - { - let base_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let mut out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - out_dir.pop(); - out_dir.pop(); - prepare_pv_recorder_lib(base_dir, out_dir); - } -} -#[cfg(feature = "dist")] -fn prepare_pv_recorder_lib(base_dir: PathBuf, build_dir: PathBuf) { - let pv_recorder_lib_path = fs::read_dir(build_dir) - .unwrap() - .find_map(|entry| { - let mut path = entry.unwrap().path(); - if path.to_str().unwrap().contains(&"pv_recorder") { - path.push("out"); - path.push("lib"); - if path.exists() { - return Some(path); - } - } - None - }) - .expect("Unable to find pv_recorder lib path"); - let mut dist_folder = base_dir.clone(); - dist_folder.push("dist"); - #[cfg(all(target_os = "windows", target_arch = "x86_64"))] - { - let windows_folder = pv_recorder_lib_path.clone().join("windows"); - let windows_x86_64_binary = windows_folder.clone().join("amd64/libpv_recorder.dll"); - let windows_x86_64_binary_dest = dist_folder.clone().join("windows/amd64"); - fs::create_dir_all(windows_x86_64_binary_dest.clone()).unwrap(); - fs::copy( - windows_x86_64_binary, - windows_x86_64_binary_dest.join("libpv_recorder.dll"), - ) - .unwrap(); - } - #[cfg(all(target_os = "linux", target_arch = "x86_64"))] - { - let linux_folder = pv_recorder_lib_path.clone().join("linux"); - let linux_x86_64_binary = linux_folder.clone().join("x86_64/libpv_recorder.so"); - let linux_x86_64_binary_dest = dist_folder.clone().join("linux/x86_64"); - fs::create_dir_all(linux_x86_64_binary_dest.clone()).unwrap(); - fs::copy( - linux_x86_64_binary, - linux_x86_64_binary_dest.join("libpv_recorder.so"), - ) - .unwrap(); - } - #[cfg(all(target_os = "linux", any(target_arch = "arm", target_arch = "aarch64")))] - let rpi_folder = pv_recorder_lib_path.clone().join("raspberry-pi"); - #[cfg(all(target_os = "linux", target_arch = "arm"))] - { - let arm11_binary = rpi_folder.clone().join("arm11/libpv_recorder.so"); - let arm11_binary_dest = dist_folder.clone().join("raspberry-pi/arm11"); - fs::create_dir_all(arm11_binary_dest.clone()).unwrap(); - fs::copy(arm11_binary, arm11_binary_dest.join("libpv_recorder.so")).unwrap(); - - let cortex_a7_binary = rpi_folder.clone().join("cortex-a7/libpv_recorder.so"); - let cortex_a7_binary_dest = dist_folder.clone().join("raspberry-pi/cortex-a7"); - fs::create_dir_all(cortex_a7_binary_dest.clone()).unwrap(); - fs::copy( - cortex_a7_binary, - cortex_a7_binary_dest.join("libpv_recorder.so"), - ) - .unwrap(); - - let cortex_a53_binary = rpi_folder.clone().join("cortex-a53/libpv_recorder.so"); - let cortex_a53_binary_dest = dist_folder.clone().join("raspberry-pi/cortex-a53"); - fs::create_dir_all(cortex_a53_binary_dest.clone()).unwrap(); - fs::copy( - cortex_a53_binary, - cortex_a53_binary_dest.join("libpv_recorder.so"), - ) - .unwrap(); - - let cortex_a72_binary = rpi_folder.clone().join("cortex-a72/libpv_recorder.so"); - let cortex_a72_binary_dest = dist_folder.clone().join("raspberry-pi/cortex-a72"); - fs::create_dir_all(cortex_a72_binary_dest.clone()).unwrap(); - fs::copy( - cortex_a72_binary, - cortex_a72_binary_dest.join("libpv_recorder.so"), - ) - .unwrap(); - } - #[cfg(all(target_os = "linux", target_arch = "aarch64"))] - { - let cortex_a53_aarch64_binary = rpi_folder - .clone() - .join("cortex-a53-aarch64/libpv_recorder.so"); - let cortex_a53_aarch64_binary_dest = - dist_folder.clone().join("raspberry-pi/cortex-a53-aarch64"); - fs::create_dir_all(cortex_a53_aarch64_binary_dest.clone()).unwrap(); - fs::copy( - cortex_a53_aarch64_binary, - cortex_a53_aarch64_binary_dest.join("libpv_recorder.so"), - ) - .unwrap(); - - let cortex_a72_aarch64_binary = rpi_folder - .clone() - .join("cortex-a72-aarch64/libpv_recorder.so"); - let cortex_a72_aarch64_binary_dest = - dist_folder.clone().join("raspberry-pi/cortex-a72-aarch64"); - fs::create_dir_all(cortex_a72_aarch64_binary_dest.clone()).unwrap(); - fs::copy( - cortex_a72_aarch64_binary, - cortex_a72_aarch64_binary_dest.join("libpv_recorder.so"), - ) - .unwrap(); - } - #[cfg(target_os = "macos")] - let mac_folder = pv_recorder_lib_path.clone().join("mac"); - #[cfg(all(target_os = "macos", target_arch = "x86_64"))] - { - let mac_x86_64_binary = mac_folder.clone().join("x86_64/libpv_recorder.dylib"); - let mac_x86_64_binary_dest = dist_folder.clone().join("mac/x86_64"); - fs::create_dir_all(mac_x86_64_binary_dest.clone()).unwrap(); - fs::copy( - mac_x86_64_binary, - mac_x86_64_binary_dest.join("libpv_recorder.dylib"), - ) - .unwrap(); - } - #[cfg(all(target_os = "macos", target_arch = "aarch64"))] - { - let mac_aarch64_binary = mac_folder.clone().join("arm64/libpv_recorder.dylib"); - let mac_aarch64_binary_dest = dist_folder.clone().join("mac/arm64"); - fs::create_dir_all(mac_aarch64_binary_dest.clone()).unwrap(); - fs::copy( - mac_aarch64_binary, - mac_aarch64_binary_dest.join("libpv_recorder.dylib"), - ) - .unwrap(); - } - println!("cargo:rerun-if-changed={:?}", dist_folder); - println!("cargo:rerun-if-changed={:?}", pv_recorder_lib_path); -} diff --git a/src/cli/build_model.rs b/src/cli/build_model.rs index bc23e33..03d8914 100644 --- a/src/cli/build_model.rs +++ b/src/cli/build_model.rs @@ -2,12 +2,10 @@ use std::{fs::File, io::BufReader}; use clap::Args; use hound::WavReader; -use rustpotter::WakewordDetectorBuilder; - -use crate::utils::enable_rustpotter_log; +use rustpotter::Wakeword; #[derive(Args, Debug)] -/// Build model file from samples +/// Creates a wakeword using RIFF wav audio files. #[clap()] pub struct BuildModelCommand { #[clap(short = 'n', long)] @@ -16,7 +14,7 @@ pub struct BuildModelCommand { #[clap(short = 'p', long)] /// Generated model path model_path: String, - #[clap(min_values = 1, required = true)] + #[clap(num_args = 1.., required = true)] /// List of sample record paths sample_path: Vec, #[clap(short = 't', long)] @@ -25,37 +23,33 @@ pub struct BuildModelCommand { #[clap(short = 'a', long)] /// Averaged threshold to configure in the generated model, overwrites the detector averaged threshold averaged_threshold: Option, - #[clap(long)] - /// Enables rustpotter debug log - debug: bool, } pub fn build(command: BuildModelCommand) -> Result<(), String> { println!("Start building {}!", command.model_path); println!("From samples:"); for path in &command.sample_path { let reader = BufReader::new(File::open(path).map_err(|err| err.to_string())?); - let wav_spec = WavReader::new(reader).map_err(|err| err.to_string())? + let wav_spec = WavReader::new(reader) + .map_err(|err| err.to_string())? .spec(); println!("{}: {:?}", path, wav_spec); } - if command.debug { - enable_rustpotter_log(); - } - let mut word_detector = WakewordDetectorBuilder::new().build(); - word_detector.add_wakeword_with_wav_files( - &command.model_name, - false, - command.averaged_threshold, + let wakeword = Wakeword::new_from_sample_files( + command.model_name.clone(), command.threshold, + command.averaged_threshold, command.sample_path, - ).map_err(|e| e.to_string())?; - match word_detector.generate_wakeword_model_file(command.model_name.clone(), command.model_path) - { + )?; + match wakeword.save_to_file(&command.model_path) { Ok(_) => { println!("{} created!", command.model_name); } Err(error) => { - clap::Error::raw(clap::ErrorKind::InvalidValue, error.to_string() + "\n").exit(); + clap::Error::raw( + clap::error::ErrorKind::InvalidValue, + error.to_string() + "\n", + ) + .exit(); } }; Ok(()) diff --git a/src/cli/devices.rs b/src/cli/devices.rs index d83b820..bfb3ef8 100644 --- a/src/cli/devices.rs +++ b/src/cli/devices.rs @@ -1,33 +1,49 @@ +extern crate cpal; use clap::Args; -use pv_recorder::RecorderBuilder; -#[cfg(feature = "dist")] -use crate::pv_recorder_utils::_get_pv_recorder_lib; -#[derive(Args, Debug)] +use cpal::traits::{DeviceTrait, HostTrait}; /// Record audio sample +#[derive(Args, Debug)] #[clap()] pub struct DevicesCommand {} pub fn devices(_: DevicesCommand) -> Result<(), String> { - #[cfg(feature = "dist")] - let mut recorder_builder = RecorderBuilder::new(); - #[cfg(not(feature = "dist"))] - let recorder_builder = RecorderBuilder::new(); - #[cfg(feature = "dist")] - let lib_temp_path = _get_pv_recorder_lib(); - #[cfg(feature = "dist")] - recorder_builder.library_path(lib_temp_path.to_path_buf().as_path()); - let recorder = recorder_builder.init() - .expect("Failed to initialize recorder"); - println!("Available record audio devices:"); - let audio_devices = recorder.get_audio_devices(); - match audio_devices { - Ok(audio_devices) => { - for (idx, device) in audio_devices.iter().enumerate() { - println!("{}: {:?}", idx, device); + println!("Supported hosts:\n {:?}", cpal::ALL_HOSTS); + let default_host = cpal::default_host(); + let host_id = default_host.id(); + println!("Using hosts:\n {:?}", default_host.id()); + println!("{}", host_id.name()); + let host = cpal::host_from_id(host_id).map_err(|err| err.to_string())?; + let default_in = host.default_input_device().map(|e| e.name().unwrap()); + if let Some(def_in) = default_in { + println!(" Default input device:\n {}", def_in); + } else { + println!(" No default input device"); + } + let devices = host.input_devices().map_err(|err| err.to_string())?; + println!(" Devices: "); + for (device_index, device) in devices.enumerate() { + println!( + " {} - \"{}\"", + device_index, + device.name().map_err(|err| err.to_string())? + ); + + // Input configs + if let Ok(conf) = device.default_input_config() { + println!(" Default input stream config:\n {:?}", conf); + } + let input_configs = match device.supported_input_configs() { + Ok(f) => f.collect(), + Err(e) => { + println!(" Error getting supported input configs: {:?}", e); + Vec::new() + } + }; + if !input_configs.is_empty() { + println!(" All supported input stream configs:"); + for (config_index, config) in input_configs.into_iter().enumerate() { + println!(" {} - {:?}", config_index, config); } } - Err(err) => panic!("Failed to get audio devices: {}", err), - }; - #[cfg(all(feature = "dist", not(target_os = "windows")))] - lib_temp_path.close().expect("Unable to remove temp file"); + } Ok(()) } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 2a44745..b0dd12a 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,37 +1,40 @@ use clap::{Parser, Subcommand}; -mod record; mod build_model; -mod test_model; -mod spot; mod devices; -use self::{record::{record,RecordCommand}, build_model::{BuildModelCommand, build}, test_model::{test, TestModelCommand}, spot::{spot, SpotCommand}, devices::{devices, DevicesCommand}}; +mod record; +mod spot; +mod test_model; +use self::{ + build_model::{build, BuildModelCommand}, + devices::{devices, DevicesCommand}, + record::{record, RecordCommand}, + spot::{spot, SpotCommand}, + test_model::{test, TestModelCommand}, +}; #[derive(Parser, Debug)] -/// RustPotter: the free personal wakeword spotter written on rust +/// CLI for RustPotter: an open source wakeword spotter forged in rust #[clap(author, version, about, long_about = None, arg_required_else_help = true)] struct CLI { #[clap(subcommand)] command: Option, } - #[derive(Subcommand, Debug)] -/// Record audio sample enum Commands { - /// Record audio sample + /// Record wav audio file Record(RecordCommand), - /// Build wakeword model + /// Build wakeword model from wav audio files BuildModel(BuildModelCommand), - /// Test model accuracy against a sample + /// Test model accuracy against a wav file TestModel(TestModelCommand), - /// Spot wakeword using model + /// Spot wakewords in real time Spot(SpotCommand), - /// List audio devices + /// List audio devices and configurations Devices(DevicesCommand), } - -pub fn run_cli() { +pub(crate) fn run_cli() { let cli = CLI::parse(); match cli.command.unwrap() { Commands::Record(command) => record(command), @@ -39,5 +42,6 @@ pub fn run_cli() { Commands::TestModel(command) => test(command), Commands::Spot(command) => spot(command), Commands::Devices(command) => devices(command), - }.expect("Command failed"); + } + .expect("Command failed"); } diff --git a/src/cli/record.rs b/src/cli/record.rs index 0477046..34c53e3 100644 --- a/src/cli/record.rs +++ b/src/cli/record.rs @@ -1,71 +1,199 @@ -use std::sync::atomic::{AtomicBool, Ordering}; +use std::fs::File; +use std::io::BufWriter; +use std::sync::{mpsc, Arc, Mutex}; use clap::Args; -use pv_recorder::RecorderBuilder; -#[cfg(feature = "dist")] -use crate::pv_recorder_utils::_get_pv_recorder_lib; -use rustpotter::{WakewordDetectorBuilder}; - +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use cpal::{FromSample, Sample}; #[derive(Args, Debug)] -/// Record wav audio with spec 16000hz 16bit 1 channel int. Press "Ctrl + c" to stop and save. +/// Record wav audio #[clap()] pub struct RecordCommand { #[clap()] /// Generated record path output_path: String, - #[clap(short, long, default_value_t = 0)] + #[clap(short = 'i', long)] + /// Input device index used for record + device_index: Option, + #[clap(short, long)] /// Input device index used for record - device_index: usize, + config_index: Option, + #[clap(short, long, default_value_t = 1.)] + /// Adjust the recording volume. value > 1.0 amplifies, value < 1.0 attenuates + gain: f32, } pub fn record(command: RecordCommand) -> Result<(), String> { - let mut detector_builder = WakewordDetectorBuilder::new(); - detector_builder.set_sample_rate(16000); - let detector = detector_builder.build(); - let mut recorder_builder = RecorderBuilder::new(); - recorder_builder.frame_length(detector.get_samples_per_frame() as i32); - recorder_builder.device_index(command.device_index as i32); - recorder_builder.log_overflow(false); - #[cfg(feature = "dist")] - let lib_temp_path = _get_pv_recorder_lib(); - #[cfg(feature = "dist")] - recorder_builder.library_path(lib_temp_path.to_path_buf().as_path()); - let recorder = recorder_builder - .device_index(command.device_index as i32) - .init() - .expect("Failed to initialize recorder"); - static LISTENING: AtomicBool = AtomicBool::new(false); - ctrlc::set_handler(|| { - LISTENING.store(false, Ordering::SeqCst); - }) - .expect("Unable to setup signal handler"); - println!("Start recording..."); - recorder.start().expect("Failed to start audio recording"); + //get the host + let host = cpal::default_host(); + + //get the default input device + // Set up the input device and stream with the default input config. + let device = get_device(command.device_index, host); + + //get default config - channels, sample_rate,buffer_size, sample_format + println!( + "Input device: {}", + device.name().map_err(|err| err.to_string())? + ); + let config = get_config(command.config_index, &device); + println!("Input device config: {:?}", config); + let spec = wav_spec_from_config(&config); + let writer = hound::WavWriter::create(command.output_path.to_string(), spec).unwrap(); + let writer = Arc::new(Mutex::new(Some(writer))); + println!("Begin recording..."); + // Run the input stream on a separate thread. + let writer_2 = writer.clone(); + let err_fn = move |err| { + eprintln!("an error occurred on stream: {}", err); + }; + let err_cb = move |err: cpal::BuildStreamError| err.to_string(); + let stream = match config.sample_format() { + cpal::SampleFormat::I16 => device + .build_input_stream( + &config.into(), + move |data, _: &_| write_input_data::(data, &writer_2, command.gain), + err_fn, + None, + ) + .map_err(err_cb)?, + cpal::SampleFormat::I32 => device + .build_input_stream( + &config.into(), + move |data, _: &_| write_input_data::(data, &writer_2, command.gain), + err_fn, + None, + ) + .map_err(err_cb)?, + cpal::SampleFormat::F32 => device + .build_input_stream( + &config.into(), + move |data, _: &_| write_input_data::(data, &writer_2, command.gain), + err_fn, + None, + ) + .map_err(err_cb)?, + _ => return Err("Only support sample formats: i16, i32, f32".to_string())?, + }; + stream.play().expect("Unable to record"); + let (tx, rx) = mpsc::channel(); + ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel.")) + .expect("Unable to listen keyboard"); println!("Press 'Ctrl + c' to stop."); - LISTENING.store(true, Ordering::SeqCst); - let mut audio_data = Vec::new(); - let mut frame_buffer = vec![0; recorder.frame_length()]; - while LISTENING.load(Ordering::SeqCst) { - recorder - .read(&mut frame_buffer) - .expect("Failed to read audio frame"); - audio_data.extend_from_slice(&frame_buffer); + rx.recv().expect("Program failed"); + drop(stream); + writer + .lock() + .unwrap() + .take() + .unwrap() + .finalize() + .expect("Unable to save file"); + println!("Recording {} complete!", &command.output_path); + Ok(()) +} + +fn write_input_data( + input: &[T], + writer: &Arc>>>>, + gain: f32, +) where + T: Sample, + U: Sample + hound::Sample + FromSample, +{ + if let Ok(mut guard) = writer.try_lock() { + if let Some(writer) = guard.as_mut() { + let gain_sample = Sample::from_sample(gain); + for &sample in input.iter() { + let sample: U = U::from_sample(sample.mul_amp(gain_sample)); + writer.write_sample(sample).ok(); + } + } } +} - println!("Stop recording..."); - recorder.stop().expect("Failed to stop audio recording"); - println!("Creating wav file {}", command.output_path); - let spec = hound::WavSpec { - channels: 1, - sample_rate: 16000, - bits_per_sample: 16, - sample_format: hound::SampleFormat::Int, - }; - let mut writer = hound::WavWriter::create(command.output_path, spec).unwrap(); - for sample in audio_data { - writer.write_sample(sample).unwrap(); +fn wav_spec_from_config(config: &cpal::SupportedStreamConfig) -> hound::WavSpec { + hound::WavSpec { + channels: config.channels() as _, + sample_rate: config.sample_rate().0 as _, + bits_per_sample: (config.sample_format().sample_size() * 8) as _, + sample_format: if config.sample_format().is_float() { + hound::SampleFormat::Float + } else { + hound::SampleFormat::Int + }, } - println!("Done"); - #[cfg(all(feature = "dist", not(target_os = "windows")))] - lib_temp_path.close().expect("Unable to remove temp file"); - Ok(()) +} + +// cpal utils for selecting input device and stream config + +pub(crate) fn is_compatible_format(format: &cpal::SampleFormat) -> bool { + match format { + cpal::SampleFormat::I16 => true, + cpal::SampleFormat::I32 => true, + cpal::SampleFormat::F32 => true, + _ => false, + } +} +pub(crate) fn is_compatible_buffer_size( + supported_buffer_size: &cpal::SupportedBufferSize, + buffer_size: u32, +) -> bool { + match *supported_buffer_size { + cpal::SupportedBufferSize::Range { max, min } => min <= buffer_size && buffer_size <= max, + // assume compatible + cpal::SupportedBufferSize::Unknown => true, + } +} + +pub(crate) fn get_config( + config_index: Option, + device: &cpal::Device, +) -> cpal::SupportedStreamConfig { + config_index.map_or_else( + || { + let default_config = device + .default_input_config() + .expect("Failed to get default input config"); + if is_compatible_format(&default_config.sample_format()) { + default_config + } else { + // look for any compatible configuration + device + .supported_input_configs() + .expect("Failed to list input configs") + .find(|sc| is_compatible_format(&sc.sample_format())) + .map(|sc| sc.with_max_sample_rate()) + .expect("Failed to get default input config") + } + }, + |config_index| { + device + .supported_input_configs() + .expect("Failed to list input configs") + .enumerate() + .find_map(|(i, d)| { + if i == config_index && is_compatible_format(&d.sample_format()) { + Some(d.with_max_sample_rate()) + } else { + None + } + }) + .expect("Unavailable or incompatible configuration selected") + }, + ) +} + +pub(crate) fn get_device(device_index: Option, host: cpal::Host) -> cpal::Device { + let device = device_index + .map_or_else( + || host.default_input_device(), + |device_index| { + host.input_devices() + .expect("Failed to list input device") + .enumerate() + .find_map(|(i, d)| if i == device_index { Some(d) } else { None }) + }, + ) + .expect("Failed to find input device"); + device } diff --git a/src/cli/spot.rs b/src/cli/spot.rs index c8f3d58..d4e4971 100644 --- a/src/cli/spot.rs +++ b/src/cli/spot.rs @@ -1,139 +1,297 @@ -use std::sync::atomic::{AtomicBool, Ordering}; +use std::{sync::mpsc, time::SystemTime}; -#[cfg(feature = "dist")] -use crate::pv_recorder_utils::_get_pv_recorder_lib; -use crate::utils::enable_rustpotter_log; +use crate::cli::record::{self, is_compatible_buffer_size}; use clap::Args; -use pv_recorder::RecorderBuilder; -use rustpotter::{NoiseDetectionMode, VadMode, WakewordDetectorBuilder}; +use cpal::traits::{DeviceTrait, StreamTrait}; +use rustpotter::{Rustpotter, RustpotterConfig, RustpotterDetection, ScoreMode}; +use time::OffsetDateTime; + #[derive(Args, Debug)] -/// Spot wakeword processing wav audio with spec 16000hz 16bit 1 channel int +/// Spot wakewords. #[clap()] pub struct SpotCommand { - #[clap(min_values = 1, required = true)] - /// Model path list + #[clap(num_args = 1.., required = true)] + /// Model path list. model_path: Vec, + #[clap(short = 'i', long)] + /// Input device index used for record. + device_index: Option, + #[clap(short, long)] + /// Input device index used for record. + config_index: Option, #[clap(short, long, default_value_t = 0.5)] - /// Default detection threshold, only applies to models without threshold + /// Default detection threshold, only applies to models without threshold. threshold: f32, + #[clap(short, long, default_value_t = 0.)] + /// Default detection averaged threshold, only applies to models without averaged threshold. + averaged_threshold: f32, + #[clap(short, long, default_value_t = 10)] + /// Minimum number of partial detections + min_scores: usize, + #[clap(short = 's', long, default_value_t = ClapScoreMode::Max)] + /// How to calculate a unified score + score_mode: ClapScoreMode, + #[clap(short = 'g', long)] + /// Enables a gain-normalizer audio filter. + gain_normalizer: bool, + #[clap(long, default_value_t = 0.1)] + /// Min gain applied by the gain-normalizer filter. + min_gain: f32, + #[clap(long, default_value_t = 1.)] + /// Max gain applied by the gain-normalizer filter. + max_gain: f32, #[clap(short, long)] - /// Default detection averaged threshold, only applies to models without averaged threshold, defaults to threshold/2. - averaged_threshold: Option, - #[clap(short, long, default_value_t = 0)] - /// Input device index used for record - device_index: usize, - #[clap(short = 'e', long)] - /// Enables eager mode - eager_mode: bool, - #[clap(long)] - /// Unless enabled the comparison against multiple wakewords run in separate threads, not applies when single wakeword - single_thread: bool, - #[clap(short = 'n', long, possible_values = ["easiest", "easy", "normal", "hard", "hardest"])] - /// Enables using the built-in noise detection - /// to reduce computation on absence of voice sound. - noise_mode: Option, - #[clap(long, default_value_t = 0.5)] - /// Voice/silence ratio in the last second to consider voice is detected - noise_sensitivity: f32, - /// Enables using a noise detector to reduce computation on absence of voice sound - #[clap(short = 'v', long, possible_values = ["low-bitrate", "quality", "aggressive", "very-aggressive"])] - vad_mode: Option, - #[clap(long, default_value_t = 3)] - /// Seconds to disable the vad detector after voice is detected - vad_delay: u16, - #[clap(long, default_value_t = 0.5)] - /// Voice/silence ratio in the last second to consider voice is detected - vad_sensitivity: f32, - #[clap(long)] - /// Enables rustpotter debug log + /// Set the rms level reference used by the gain normalizer filter. + /// If unset the max wakeword rms level is used. + gain_ref: Option, + #[clap(short, long)] + /// Enables a band-pass audio filter. + band_pass: bool, + #[clap(long, default_value_t = 80.)] + /// Band-pass audio filter low cutoff. + low_cutoff: f32, + #[clap(long, default_value_t = 400.)] + /// Band-pass audio filter high cutoff. + high_cutoff: f32, + #[clap(long, default_value_t = 5)] + /// Band size of the comparison. (Advanced) + comparator_band_size: u16, + #[clap(long, default_value_t = 0.22)] + /// Used to express the result as a probability. (Advanced) + comparator_ref: f32, + #[clap(short, long)] + /// Log partial detections. debug: bool, + #[clap(long)] + /// Log rms level ref, gain applied per frame and frame rms level. + debug_gain: bool, } pub fn spot(command: SpotCommand) -> Result<(), String> { println!("Spotting using models: {:?}!", command.model_path); + // select input device and config + let host = cpal::default_host(); + let device = record::get_device(command.device_index, host); + println!( + "Input device: {}", + device.name().map_err(|err| err.to_string())? + ); + let device_config = record::get_config(command.config_index, &device); + println!("Input device config: {:?}", device_config); + // configure rustpotter + let mut config = RustpotterConfig::default(); + config.fmt.sample_rate = device_config.sample_rate().0 as _; + config.fmt.bits_per_sample = (device_config.sample_format().sample_size() * 8) as _; + config.fmt.channels = device_config.channels(); + config.fmt.sample_format = if device_config.sample_format().is_float() { + hound::SampleFormat::Float + } else { + hound::SampleFormat::Int + }; + config.detector.avg_threshold = command.averaged_threshold; + config.detector.threshold = command.threshold; + config.detector.min_scores = command.min_scores; + config.detector.score_mode = command.score_mode.into(); + config.detector.comparator_band_size = command.comparator_band_size; + config.detector.comparator_ref = command.comparator_ref; + config.filters.gain_normalizer.enabled = command.gain_normalizer; + config.filters.gain_normalizer.gain_ref = command.gain_ref; + config.filters.gain_normalizer.min_gain = command.min_gain; + config.filters.gain_normalizer.max_gain = command.max_gain; + config.filters.band_pass.enabled = command.band_pass; + config.filters.band_pass.low_cutoff = command.low_cutoff; + config.filters.band_pass.high_cutoff = command.high_cutoff; if command.debug { - enable_rustpotter_log(); + println!("Rustpotter config:\n{:?}", config); } - let mut detector_builder = WakewordDetectorBuilder::new(); - if command.averaged_threshold.is_some() { - detector_builder.set_averaged_threshold(command.averaged_threshold.unwrap()); + let mut rustpotter = Rustpotter::new(&config)?; + if !is_compatible_buffer_size( + &device_config.buffer_size(), + rustpotter.get_samples_per_frame() as u32, + ) { + clap::Error::raw( + clap::error::ErrorKind::Io, + "Rustpotter required buffer size does not matches device configuration, try selecting other.\n", + ) + .exit(); } - if command.vad_mode.is_some() { - detector_builder - .set_vad_mode(get_vad_mode(&command.vad_mode.unwrap())) - .set_vad_delay(command.vad_delay) - .set_vad_sensitivity(command.vad_sensitivity); - } - if command.noise_mode.is_some() { - detector_builder - .set_noise_mode(get_noise_mode(&command.noise_mode.unwrap())) - .set_noise_sensitivity(command.noise_sensitivity); - } - let mut word_detector = detector_builder - .set_threshold(command.threshold) - .set_sample_rate(16000) - .set_eager_mode(command.eager_mode) - .set_single_thread(command.single_thread) - .build(); for path in command.model_path { - let result = word_detector.add_wakeword_from_model_file(path, true); + let result = rustpotter.add_wakeword_from_file(&path); if let Err(error) = result { - clap::Error::raw(clap::ErrorKind::InvalidValue, error.to_string() + "\n").exit(); + clap::Error::raw(clap::error::ErrorKind::InvalidValue, error + "\n").exit(); } } - let mut recorder_builder = RecorderBuilder::new(); - recorder_builder.frame_length((word_detector.get_samples_per_frame()) as i32); - recorder_builder.buffer_size_msec(word_detector.get_samples_per_frame() as i32 * 2); - recorder_builder.device_index(command.device_index as i32); - recorder_builder.log_overflow(false); - #[cfg(feature = "dist")] - let lib_temp_path = _get_pv_recorder_lib(); - #[cfg(feature = "dist")] - recorder_builder.library_path(lib_temp_path.to_path_buf().as_path()); - let recorder = recorder_builder - .init() - .expect("Failed to initialize recorder"); - static LISTENING: AtomicBool = AtomicBool::new(false); - ctrlc::set_handler(|| { - LISTENING.store(false, Ordering::SeqCst); - }) - .expect("Error setting Ctrl-C handler"); - recorder.start().expect("Failed to start audio recording"); - LISTENING.store(true, Ordering::SeqCst); - while LISTENING.load(Ordering::SeqCst) { - let mut frame_buffer = vec![0; recorder.frame_length()]; - recorder - .read(&mut frame_buffer) - .expect("Failed to read audio frame"); - let detections = word_detector.process_i16(&frame_buffer); - for detection in &detections { - println!( - "Detected '{}' with score {}!", - detection.wakeword, detection.score - ) - } + if command.debug_gain { + println!( + "Gain Normalizer RMS level reference: {}", + rustpotter.get_rms_level_ref() + ); } + println!("Begin recording..."); + let err_fn = move |err| { + eprintln!("an error occurred on stream: {}", err); + }; + let err_cb = move |err: cpal::BuildStreamError| err.to_string(); + let stream_config = cpal::StreamConfig { + channels: device_config.channels(), + sample_rate: device_config.sample_rate(), + buffer_size: cpal::BufferSize::Fixed(rustpotter.get_samples_per_frame() as u32), + }; + let mut partial_detection_counter = 0; + let stream = match device_config.sample_format() { + cpal::SampleFormat::I16 => device + .build_input_stream( + &stream_config, + move |data: &[i16], _: &_| { + let detection = rustpotter.process_i16(data); + print_detection( + &rustpotter, + detection, + &mut partial_detection_counter, + command.debug, + command.debug_gain, + ); + }, + err_fn, + None, + ) + .map_err(err_cb)?, + cpal::SampleFormat::I32 => device + .build_input_stream( + &stream_config, + move |data: &[i32], _: &_| { + let detection = rustpotter.process_i32(data); + print_detection( + &rustpotter, + detection, + &mut partial_detection_counter, + command.debug, + command.debug_gain, + ); + }, + err_fn, + None, + ) + .map_err(err_cb)?, + cpal::SampleFormat::F32 => device + .build_input_stream( + &stream_config, + move |data: &[f32], _: &_| { + let detection = rustpotter.process_f32(data); + print_detection( + &rustpotter, + detection, + &mut partial_detection_counter, + command.debug, + command.debug_gain, + ); + }, + err_fn, + None, + ) + .map_err(err_cb)?, + _ => return Err("Only support sample formats: i16, i32, f32".to_string())?, + }; + stream.play().expect("Unable to record"); + let (tx, rx) = mpsc::channel(); + ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel.")) + .expect("Error setting Ctrl-C handler"); + println!("Press 'Ctrl + c' to stop."); + rx.recv().expect("Program failed"); + drop(stream); println!("Stopped by user request"); - #[cfg(all(feature = "dist", not(target_os = "windows")))] - lib_temp_path.close().expect("Unable to remove temp file"); Ok(()) } -fn get_vad_mode(name: &str) -> VadMode { - match name { - "low-bitrate" => VadMode::LowBitrate, - "quality" => VadMode::Quality, - "aggressive" => VadMode::Aggressive, - "very-aggressive" => VadMode::VeryAggressive, - _ => clap::Error::raw(clap::ErrorKind::InvalidValue, "Unsupported vad mode.\n").exit(), + +pub(crate) fn print_detection( + rustpotter: &Rustpotter, + detection: Option, + partial_detection_counter: &mut usize, + debug: bool, + debug_gain: bool, +) { + if debug_gain { + println!( + "Frame volume info: RMS={}, Gain={}", + rustpotter.get_rms_level(), + rustpotter.get_gain() + ) + } + let dt: OffsetDateTime = SystemTime::now().into(); + let partial_detection = rustpotter.get_partial_detection(); + *partial_detection_counter = match detection { + Some(detection) => { + println!( + "Wakeword detection: [{:02}:{:02}:{:02}] {:?}", + dt.hour(), + dt.minute(), + dt.second(), + detection + ); + 0 + } + None => partial_detection.map_or_else( + || { + if debug && *partial_detection_counter > 0 { + println!("Partial detection discarded"); + } + 0 + }, + |detection| { + if debug && *partial_detection_counter < detection.counter { + println!( + "Partial detected: [{:02}:{:02}:{:02}] {:?}", + dt.hour(), + dt.minute(), + dt.second(), + detection + ); + } + detection.counter + }, + ), + }; +} + +#[derive(clap::ValueEnum, Clone, Debug)] +pub(crate) enum ClapScoreMode { + Max, + Avg, + Median, + P25, + P50, + P75, + P80, + P90, + P95, +} +impl std::fmt::Display for ClapScoreMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ClapScoreMode::Avg => write!(f, "avg"), + ClapScoreMode::Max => write!(f, "max"), + ClapScoreMode::Median => write!(f, "median"), + ClapScoreMode::P25 => write!(f, "p25"), + ClapScoreMode::P50 => write!(f, "p50"), + ClapScoreMode::P75 => write!(f, "p75"), + ClapScoreMode::P80 => write!(f, "p80"), + ClapScoreMode::P90 => write!(f, "p90"), + ClapScoreMode::P95 => write!(f, "p95"), + } } } -fn get_noise_mode(name: &str) -> NoiseDetectionMode { - match name { - "easiest" => NoiseDetectionMode::Easiest, - "easy" => NoiseDetectionMode::Easy, - "normal" => NoiseDetectionMode::Normal, - "hard" => NoiseDetectionMode::Hard, - "hardest" => NoiseDetectionMode::Hardest, - _ => clap::Error::raw(clap::ErrorKind::InvalidValue, "Unsupported vad mode.\n").exit(), +impl From for ScoreMode { + fn from(value: ClapScoreMode) -> Self { + match value { + ClapScoreMode::Avg => ScoreMode::Average, + ClapScoreMode::Max => ScoreMode::Max, + ClapScoreMode::Median => ScoreMode::Median, + ClapScoreMode::P25 => ScoreMode::P25, + ClapScoreMode::P50 => ScoreMode::P50, + ClapScoreMode::P75 => ScoreMode::P75, + ClapScoreMode::P80 => ScoreMode::P80, + ClapScoreMode::P90 => ScoreMode::P90, + ClapScoreMode::P95 => ScoreMode::P95, + } } } diff --git a/src/cli/test_model.rs b/src/cli/test_model.rs index 22afb96..6deb16f 100644 --- a/src/cli/test_model.rs +++ b/src/cli/test_model.rs @@ -1,94 +1,142 @@ use clap::Args; use hound::{SampleFormat, WavReader}; -use rustpotter::WakewordDetectorBuilder; +use rustpotter::{Rustpotter, RustpotterConfig}; use std::{fs::File, io::BufReader}; -use crate::utils::enable_rustpotter_log; +use super::spot::{print_detection, ClapScoreMode}; #[derive(Args, Debug)] /// Test model file against a wav sample, detector is automatically configured according to the sample spec #[clap()] pub struct TestModelCommand { #[clap()] - /// Output model path + /// Model to test. model_path: String, #[clap()] - /// Sample record path + /// Wav record to test. sample_path: String, #[clap(short, long, default_value_t = 0.5)] - /// Default detection threshold, only applies to models without threshold + /// Default detection threshold, only applies to models without threshold. threshold: f32, #[clap(short, long, default_value_t = 0.2)] - /// Default detection averaged threshold, only applies to models without averaged threshold + /// Default detection averaged threshold, only applies to models without averaged threshold. averaged_threshold: f32, - #[clap(long)] - /// Enables rustpotter debug log + #[clap(short, long, default_value_t = 10)] + /// Minimum number of partial detections + min_scores: usize, + #[clap(short = 's', long, default_value_t = ClapScoreMode::Max)] + /// How to calculate a unified score + score_mode: ClapScoreMode, + #[clap(short = 'g', long)] + /// Enables a gain-normalizer audio filter. + gain_normalizer: bool, + #[clap(long, default_value_t = 0.1)] + /// Min gain applied by the gain-normalizer filter. + min_gain: f32, + #[clap(long, default_value_t = 1.)] + /// Max gain applied by the gain-normalizer filter. + max_gain: f32, + #[clap(short, long)] + /// Set the rms level reference used by the gain normalizer filter. + /// If unset the max wakeword rms level is used. + gain_ref: Option, + #[clap(short, long)] + /// Enables a band-pass audio filter. + band_pass: bool, + #[clap(long, default_value_t = 80.)] + /// Band-pass audio filter low cutoff. + low_cutoff: f32, + #[clap(long, default_value_t = 400.)] + /// Band-pass audio filter high cutoff. + high_cutoff: f32, + #[clap(long, default_value_t = 5)] + /// Band size of the comparison. (Advanced) + comparator_band_size: u16, + #[clap(long, default_value_t = 0.22)] + /// Used to express the result as a probability. (Advanced) + comparator_ref: f32, + #[clap(short, long)] + /// Log partial detections. debug: bool, + #[clap(long)] + /// Log rms level ref, gain applied per frame and frame rms level. + debug_gain: bool, } pub fn test(command: TestModelCommand) -> Result<(), String> { println!( "Testing file {} against model {}!", command.sample_path, command.model_path, ); - if command.debug { - enable_rustpotter_log(); - } - let mut detector_builder = WakewordDetectorBuilder::new(); - let reader = + // Read wav file + let file_reader = BufReader::new(File::open(command.sample_path).map_err(|err| err.to_string())?); - let mut wav_reader = WavReader::new(reader).map_err(|err| err.to_string())?; + let mut wav_reader = WavReader::new(file_reader).map_err(|err| err.to_string())?; let wav_specs = wav_reader.spec(); - let mut word_detector = detector_builder - .set_averaged_threshold(command.averaged_threshold) - .set_threshold(command.threshold) - .set_sample_rate(wav_specs.sample_rate as usize) - .set_bits_per_sample(wav_specs.bits_per_sample) - .set_sample_format(wav_specs.sample_format) - .set_channels(wav_specs.channels) - .build(); - if let Err(error) = word_detector.add_wakeword_from_model_file(command.model_path, true) { + let mut config = RustpotterConfig::default(); + config.fmt.sample_rate = wav_specs.sample_rate as usize; + config.fmt.bits_per_sample = wav_specs.bits_per_sample; + config.fmt.channels = wav_specs.channels; + config.detector.avg_threshold = command.averaged_threshold; + config.detector.threshold = command.threshold; + config.detector.min_scores = command.min_scores; + config.detector.score_mode = command.score_mode.into(); + config.detector.comparator_band_size = command.comparator_band_size; + config.detector.comparator_ref = command.comparator_ref; + config.filters.gain_normalizer.enabled = command.gain_normalizer; + config.filters.gain_normalizer.gain_ref = command.gain_ref; + config.filters.gain_normalizer.min_gain = command.min_gain; + config.filters.gain_normalizer.max_gain = command.max_gain; + config.filters.band_pass.enabled = command.band_pass; + config.filters.band_pass.low_cutoff = command.low_cutoff; + config.filters.band_pass.high_cutoff = command.high_cutoff; + let mut rustpotter = Rustpotter::new(&config)?; + if let Err(error) = rustpotter.add_wakeword_from_file(&command.model_path) { clap::Error::raw( - clap::ErrorKind::InvalidValue, + clap::error::ErrorKind::InvalidValue, error.to_string() + "\n", ) .exit(); } + let mut partial_detection_counter = 0; match wav_specs.sample_format { SampleFormat::Int => { let mut buffer = wav_reader .samples::() .map(Result::unwrap) .collect::>(); - buffer.append(&mut vec![0; word_detector.get_samples_per_frame()]); + buffer.append(&mut vec![0; rustpotter.get_samples_per_frame() * 100]); buffer - .chunks_exact(word_detector.get_samples_per_frame()) - .filter_map(|chunk| word_detector.process(chunk)) - .for_each(|detection| { - println!( - "Detected '{}' with score {}!", - detection.wakeword, detection.score - ) + .chunks_exact(rustpotter.get_samples_per_frame()) + .for_each(|chunk| { + let detection = rustpotter.process_i32(chunk); + print_detection( + &rustpotter, + detection, + &mut partial_detection_counter, + command.debug, + command.debug_gain, + ); }); - println!("Done!"); - Ok(()) } SampleFormat::Float => { let mut buffer = wav_reader .samples::() .map(Result::unwrap) .collect::>(); - buffer.append(&mut vec![0.; word_detector.get_samples_per_frame()]); + buffer.append(&mut vec![0.; rustpotter.get_samples_per_frame() * 100]); buffer - .chunks_exact(word_detector.get_samples_per_frame()) - .filter_map(|chunk| word_detector.process_f32(chunk)) - .for_each(|detection| { - println!( - "Detected '{}' with score {}!", - detection.wakeword, detection.score - ) + .chunks_exact(rustpotter.get_samples_per_frame()) + .for_each(|chunk| { + let detection = rustpotter.process_f32(chunk); + print_detection( + &rustpotter, + detection, + &mut partial_detection_counter, + command.debug, + command.debug_gain, + ); }); - println!("Done!"); - Ok(()) } - } + }; + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 9b827ff..2f1b12f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,4 @@ mod cli; -mod pv_recorder_utils; -mod utils; -use cli::run_cli; fn main() { - run_cli(); + cli::run_cli(); } diff --git a/src/pv_recorder_utils.rs b/src/pv_recorder_utils.rs deleted file mode 100644 index 5e2f470..0000000 --- a/src/pv_recorder_utils.rs +++ /dev/null @@ -1,106 +0,0 @@ -#[cfg(feature = "dist")] -use include_dir::{include_dir, Dir}; -#[cfg(feature = "dist")] -use std::io::Write; -#[cfg(feature = "dist")] -use std::path::PathBuf; -#[cfg(feature = "dist")] -use tempfile::{Builder as TempFileBuilder, TempPath}; -#[cfg(feature = "dist")] -static _DIST_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/dist"); -#[cfg(feature = "dist")] -pub fn _get_pv_recorder_lib() -> TempPath { - let rel_path = _base_library_path(); - let native_lib = _DIST_DIR.get_file(rel_path).unwrap(); - let mut file = TempFileBuilder::new() - .prefix("pv_recorder") - .suffix(".tmp") - .rand_bytes(8) - .tempfile() - .unwrap(); - file.write_all(native_lib.contents()) - .expect("Unable to write to temporal file"); - file.flush().expect("Unable to write to temporal file"); - file.into_temp_path() -} - -#[cfg(all(feature = "dist", target_os = "linux", any(target_arch = "arm", target_arch = "aarch64")))] -fn find_machine_type() -> String { - use std::process::Command; - - let cpu_info = Command::new("cat") - .arg("/proc/cpuinfo") - .output() - .expect("Failed to retrieve cpu info"); - let cpu_part_list = std::str::from_utf8(&cpu_info.stdout) - .unwrap() - .split("\n") - .filter(|x| x.contains("CPU part")) - .collect::>(); - - if cpu_part_list.len() == 0 { - panic!("Unsupported CPU"); - } - - let cpu_part = cpu_part_list[0] - .split(" ") - .collect::>() - .pop() - .unwrap() - .to_lowercase(); - - let machine = match cpu_part.as_str() { - "0xb76" => "arm11", - "0xc07" => "cortex-a7", - "0xd03" => "cortex-a53", - "0xd07" => "cortex-a57", - "0xd08" => "cortex-a72", - "0xc08" => "beaglebone", - _ => "unsupported", - }; - - String::from(machine) -} - -#[cfg(all(feature = "dist", target_os = "macos", target_arch = "x86_64"))] -fn _base_library_path() -> PathBuf { - PathBuf::from("mac/x86_64/libpv_recorder.dylib") -} - -#[cfg(all(feature = "dist", target_os = "macos", target_arch = "aarch64"))] -fn _base_library_path() -> PathBuf { - PathBuf::from("mac/arm64/libpv_recorder.dylib") -} - -#[cfg(all(feature = "dist", target_os = "windows"))] -fn _base_library_path() -> PathBuf { - PathBuf::from("windows/amd64/libpv_recorder.dll") -} - -#[cfg(all(feature = "dist", target_os = "linux", target_arch = "x86_64"))] -fn _base_library_path() -> PathBuf { - PathBuf::from("linux/x86_64/libpv_recorder.so") -} - -#[cfg(all(feature = "dist", target_os = "linux", any(target_arch = "arm", target_arch = "aarch64")))] -fn _base_library_path() -> PathBuf { - const RPI_MACHINES: [&str; 4] = ["arm11", "cortex-a7", "cortex-a53", "cortex-a72"]; - - let machine = find_machine_type(); - match machine.as_str() { - machine if RPI_MACHINES.contains(&machine) => { - if cfg!(target_arch = "aarch64") { - PathBuf::from(format!( - "raspberry-pi/{}-aarch64/libpv_recorder.so", - &machine - )) - } else { - PathBuf::from(format!("raspberry-pi/{}/libpv_recorder.so", &machine)) - } - } - _ => { - eprintln!("WARNING: Falling back to the armv6-based (Raspberry Pi Zero) library. This is not tested nor optimal.\nFor the model, use Raspberry Pi's models"); - PathBuf::from("raspberry-pi/arm11/libpv_recorder.so") - } - } -} diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index b37eb5b..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub fn enable_rustpotter_log() { - simple_logger::SimpleLogger::new() - .with_level(log::LevelFilter::Warn) - .with_module_level("rustpotter", log::LevelFilter::Debug) - .init() - .unwrap(); -}