diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 833 | ||||
-rw-r--r-- | Cargo.toml | 33 | ||||
-rw-r--r-- | colourschemes/adam_neely.json | 14 | ||||
-rw-r--r-- | colourschemes/notes.txt | 27 | ||||
-rw-r--r-- | colourschemes/tango.json | 9 | ||||
-rw-r--r-- | colourschemes/wavy1.json | 9 | ||||
-rw-r--r-- | colourschemes/wavy2.json | 9 | ||||
-rw-r--r-- | colourschemes/wavy3.json | 9 | ||||
-rw-r--r-- | src/args.rs | 140 | ||||
-rw-r--r-- | src/bin/loopbacker.rs | 48 | ||||
-rw-r--r-- | src/bin/smolguitar.rs | 44 | ||||
-rw-r--r-- | src/bin/test_serde.rs | 22 | ||||
-rw-r--r-- | src/bin/testrfft.rs | 20 | ||||
-rw-r--r-- | src/buf.rs | 244 | ||||
-rw-r--r-- | src/lib.rs | 6 | ||||
-rw-r--r-- | src/main.rs | 66 | ||||
-rw-r--r-- | src/notes.rs | 168 | ||||
-rw-r--r-- | src/outputers.rs | 266 | ||||
-rw-r--r-- | src/proc.rs | 162 | ||||
-rw-r--r-- | src/rfft.rs | 179 |
21 files changed, 2309 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..485fb13 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,833 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "alsa" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44" +dependencies = [ + "alsa-sys", + "bitflags", + "libc", + "nix", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "ansi_colours" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db9d9767fde724f83933a716ee182539788f293828244e9d999695ce0f7ba1e" +dependencies = [ + "rgb", +] + +[[package]] +name = "anstream" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "crossterm" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "serde", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +dependencies = [ + "winapi", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "libc" +version = "0.2.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" + +[[package]] +name = "linux-raw-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + +[[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 = "num-complex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[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 0.45.0", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df7f93fd637f083201473dab4fee2db4c429d32e55e3299980ab3957ab916a0" +dependencies = [ + "num-integer", +] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc0d032bccba900ee32151ec0265667535c230169f5a011154cdcd984e16829" +dependencies = [ + "bitflags", + "cassowary", + "crossterm", + "serde", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "realfft" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6b8e8f0c6d2234aa58048d7290c60bf92cd36fd2888cd8331c66ad4f2e1d2" +dependencies = [ + "rustfft", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rgb" +version = "0.8.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustfft" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d4f6cbdb180c9f4b2a26bbf01c4e647f1e1dea22fe8eb9db54198b32f9434" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", + "version_check", +] + +[[package]] +name = "rustix" +version = "0.37.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustynotes" +version = "0.1.0" +dependencies = [ + "alsa", + "ansi_colours", + "clap", + "crossterm", + "derive_more", + "num-traits", + "ratatui", + "realfft", + "rustfft", + "serde", + "serde_json", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "transpose" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6522d49d03727ffb138ae4cbc1283d3774f0d10aa7f9bf52e6784c45daf9b23" +dependencies = [ + "num-integer", + "strength_reduce", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1f08b77 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "rustynotes" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[profile.release] +opt-level = 3 +# incremental seems to allow the compiler to save autovectorization +# info and makes the fold version autovectorize properly :o +incremental = true + +[dependencies] +alsa = "0.7.0" +derive_more = {version = "0.99.17", features=["display"]} +rustfft = "6.1.0" +realfft = "3.2.0" +# TODO: crossterm 0.25 currently because tui hasn't updated to 0.26 yet, +# update this dependency when tui updates to 0.26 +# (added serde here, double check that doesn't break performance again) +crossterm = { version = "0.26", features = ["serde"] } +#tui = "0.19.0" +# serde and larger things like clap mess with the autovectorization in the main code i think (or make runtime slower atleast), gotta explicitly vectorize and see if that helps or if it's something else going on +# even without actually using them just including them as deps messes with it, unless they're disabling incremental linking or forcing lower opt-levels +# weirdly building both of them with features in release gets rid of the spikes up to 4-7%, for now atleast :o, but individually they both start causing spikes :o +# building both together did cause serde to update to 1.0.160, so maybe problems with older versions of serde or somethins +tui = { package = "ratatui", version = "0.20.1", features = ["serde"] } +clap = { version = "4.2.2", features = ["derive"] } +num-traits = "0.2.15" +serde_json = "1.0.96" +serde = { version = "1.0.160", features = ["derive"] } +ansi_colours = "1.2.1" diff --git a/colourschemes/adam_neely.json b/colourschemes/adam_neely.json new file mode 100644 index 0000000..3439755 --- /dev/null +++ b/colourschemes/adam_neely.json @@ -0,0 +1,14 @@ +{ + "a": "rgb_(247,0,49)", + "as_": "rgb_(155,0,106)", + "b": "rgb_(1,0,200)", + "c": "rgb_(255,238,72)", + "cs": "rgb_(186,241,106)", + "d": "rgb_(72,247,163)", + "ds": "rgb_(84,221,168)", + "e": "rgb_(165,37,200)", + "f": "rgb_(86,230,89)", + "fs": "rgb_(89,208,84)", + "g": "rgb_(109,53,47)", + "gs": "rgb_(161,33,48)" +} diff --git a/colourschemes/notes.txt b/colourschemes/notes.txt new file mode 100644 index 0000000..07d75f9 --- /dev/null +++ b/colourschemes/notes.txt @@ -0,0 +1,27 @@ +adam_neely.json: + * he doesn't mention if sharps/flats have their own colours for him in + his video, right now i'm probably going to make them a + blended colour between the note and the next note, hopefully choosing + decent colours + * using brighter versions for the sharps also seems like a good idea, + (or darker for flats if you go the other way) that could look weird + though because i'm using brightness for value too, i might need to also + make the brightness for the max value rgb higher too for the b fex + so it doesn't get too dark too fast (probably a good idea to make sure + the notes have atleast close brightnesses to each other via the val + value in kcolorchooser) + +wavy2: + * created with: + hsv.py 344,90,255 30,80,255 30 0.23 + * strikes a bit of a balance, probably a bit hard to read still + +wavy3: + * created with: + hsv.py 344,90,255 40,80,255 24 0.22 + and + hsv.py 134,66,255 + to generate the g# colour + * with the e colour switched to g and visa-versa (green <-> yellow) + and the new g# colour generated as above + diff --git a/colourschemes/tango.json b/colourschemes/tango.json new file mode 100644 index 0000000..cbde1b6 --- /dev/null +++ b/colourschemes/tango.json @@ -0,0 +1,9 @@ +{ + "a": "dark_red", "as_": "red", + "b": "white", + "c": "dark_green", "cs": "green", + "d": "dark_cyan", "ds": "cyan", + "e": "yellow", + "f": "dark_magenta", "fs": "magenta", + "g": "dark_blue", "gs": "blue" +} diff --git a/colourschemes/wavy1.json b/colourschemes/wavy1.json new file mode 100644 index 0000000..63bd02f --- /dev/null +++ b/colourschemes/wavy1.json @@ -0,0 +1,9 @@ +{ + "c": "rgb_(255,175,237)", "cs": "rgb_(231,175,255)", + "d": "rgb_(185,175,255)", "ds": "rgb_(175,210,255)", + "e": "rgb_(175,249,255)", + "f": "rgb_(175,255,211)", "fs": "rgb_(190,255,175)", + "g": "rgb_(213,255,175)", "gs": "rgb_(243,255,175)", + "a": "rgb_(255,217,175)", "as_": "rgb_(255,199,175)", + "b": "rgb_(255,175,199)" +} diff --git a/colourschemes/wavy2.json b/colourschemes/wavy2.json new file mode 100644 index 0000000..d6101b9 --- /dev/null +++ b/colourschemes/wavy2.json @@ -0,0 +1,9 @@ +{ + "c": "rgb_(165,165,255)", "cs": "rgb_(195,207,255)", + "d": "rgb_(165,243,255)", "ds": "rgb_(195,255,251)", + "e": "rgb_(165,255,189)", + "f": "rgb_(219,255,165)", "fs": "rgb_(243,255,195)", + "g": "rgb_(255,213,165)", "gs": "rgb_(255,215,195)", + "a": "rgb_(255,165,189)", "as_": "rgb_(255,195,223)", + "b": "rgb_(243,165,255)" +} diff --git a/colourschemes/wavy3.json b/colourschemes/wavy3.json new file mode 100644 index 0000000..7d81fca --- /dev/null +++ b/colourschemes/wavy3.json @@ -0,0 +1,9 @@ +{ + "c": "rgb_(171,165,255)", "cs": "rgb_(189,197,255)", + "d": "rgb_(165,234,255)", "ds": "rgb_(189,252,255)", + "e": "rgb_(255,231,165)", + "f": "rgb_(204,255,165)", "fs": "rgb_(230,255,189)", + "g": "rgb_(165,255,201)", "gs": "rgb_(189,255,204)", + "a": "rgb_(255,165,189)", "as_": "rgb_(255,189,219)", + "b": "rgb_(246,165,255)" +} diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..fe4737e --- /dev/null +++ b/src/args.rs @@ -0,0 +1,140 @@ +use clap::{Parser, ValueEnum, Args}; +use crate::outputers::{ColourScheme, Outputers, SimpleOutputer, LineLayout, TuiOutputer}; +use derive_more::Display; +use crate::notes::Note; +use std::path::{PathBuf, Path}; +use std::error::Error; +use std::fs::File; +use std::io::BufReader; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Display)] +pub enum OutputerTypes { + #[display(fmt="simple")] + Simple, + #[display(fmt="line-layout")] + LineLayout, + #[display(fmt="tui")] + Tui, +} + +// TODO: it might make sense to add a step and quanitize values (they're quanitized to 1/i16::MAX +// anyway because it's 16bit signed ints from alsa), but i think just scaling by +// max_threshold will work for now + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct SimpleArgs { + /// outputter to use + #[arg(short, long, default_value_t = OutputerTypes::Simple)] + pub outputer: OutputerTypes, + + /// threshold for displaying the value, the values have a range of + /// 0 - 1 for the most part, a square wave at the max volume + /// of a single note will be ~1 (pure sine will be ~sqrt(0.5) or ~0.707ish) + /// but in practice most sounds won't get close to that, normally being in + /// the range of 0.005-0.10 will give you something usable without being too noisy, + /// highly depending on your input though + #[arg(short, long, default_value_t = 0.00526532149076897)] + pub threshold: f32, + + /// max (saturating) threshold, for colour output in LineLayout and maybe with + /// certain other outputers this is used as a value for which any values higher + /// then this is considered a max value and displayed at full brightness. + /// + /// For LineLayout this contols the brightness of the colour output, + /// anything >= max_threshold will display at full brightness and anything below + /// will be scaled linearly, like brightness = min(1, note_val / max_threshold) and of course + /// r = r_max * brightness, g = g_max * brightness, b = b_max * brightness. + /// + /// it's recommended to set this somewhat close to what you + /// expect your highest value is going to be (you can play with that by experimenting + /// with the Simple output, in Simple output each value is scaled by 10,000 so 2000 for + /// example would be 0.2) + /// + /// because the max sine wave volume is sqrt(0.5) for now this defaults to sqrt(0.5), but + /// it's recommended to set it lower especially if the brightness of the bars seems too low for + /// your input. + /// + /// 0.2 - 0.3 seems like an actual good value in practice, but it's worth playing with + #[arg(short, long, default_value_t = 0.7071067811865476)] + pub max_threshold: f32, + + /// don't use colour to output for outputers that support it + /// (currently LineLayout) + #[arg(long, default_value_t = false)] + pub plain: bool, + + /*/// max brightness colour for LineLayout and maybe other outputers, + /// this is the r/g/b colour at >= max_threshold, values + /// threshold > val > max_threshold will scale linearly from + /// brightness = threshold to brightness = 1, currently + /// threshold just determines if the value is displayed and not + /// its colour, that's purely from the max threshold + /// (though that could be something to change, make the calculation something like + /// diff = note_val - threshold + /// max_diff = max_threshold - threshold + /// brightness = diff / max_diff) + /// + /// These should have a range of (0.0 - 255.0) because each value gets converted + /// to a u8 to determine the final rgb colour + #[arg(long, default_value_t = String::from("78ffdc"))] + pub colour: String,*/ + + + /// File that contains the colourscheme as JSON, see the colourschemes/ directory for examples + /// we'll use adam neely's as the default for the moment, but this should be changed + /// TODO + /// NOTE (The default here is set at compile time, so you have to recompile if you change the + /// dir you're in) + #[arg(short, long)] + colours: Option<PathBuf>, + + /// Output device + #[arg(long)] + pub outdev: Option<Option<String>> +} + +impl SimpleArgs { + pub fn get_outputer(&self, notes: &[Note]) -> Outputers { + match self.outputer { + OutputerTypes::Simple => Outputers::Simple(SimpleOutputer), + // TODO: change this unwrap to a ? and a Result return value + OutputerTypes::LineLayout => Outputers::LineLayout(LineLayout::new(self.max_threshold, !self.plain, self.get_colours().unwrap(), ¬es)), + OutputerTypes::Tui => Outputers::Tui(TuiOutputer::new().unwrap()), + } + } + + pub fn get_outdev(&self) -> Option<String> { + match &self.outdev { + Some(outdev) => outdev.clone(), + None => Some(String::from("default")), + } + } + + pub fn get_colours(&self) -> Result<ColourScheme, Box<dyn Error>> { + match (self.plain, &self.colours) { + (true, _) => Ok(ColourScheme::default()), + (false, None) => Ok(ColourScheme::default()), + (false, Some(cf)) => { + // first check if the file exists either as an absolute path or in the current + // directory, if it does try to load it + let mut fres = File::open(&cf); + // otherwise try to load the colourscheme from colourschemes/ in the project directory + if fres.is_err() { + //let pth: PathBuf = [env!("CARGO_MANIFEST_DIR"), "colourschemes", cf.as_path()].iter().collect(); + let mut pth: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + pth.push("colourschemes"); + pth.push(&cf); + fres = File::open(pth); + } + let file = fres?; + let reader = BufReader::new(file); + let colours: ColourScheme = serde_json::from_reader(reader)?; + // if either loaded successfully, make sure all of the colours are Rgb for later + Ok(colours) + } + } + } + +} + diff --git a/src/bin/loopbacker.rs b/src/bin/loopbacker.rs new file mode 100644 index 0000000..ec3d136 --- /dev/null +++ b/src/bin/loopbacker.rs @@ -0,0 +1,48 @@ +use rustynotes::notes::{Notes, note_range, ProcerNotes}; +use rustynotes::proc::{Procer, ProcerData}; +use rustynotes::rfft::RFftProcer; +use rustynotes::buf::{StaticBuffer, Procers}; +use rustynotes::outputers::{Outputers, SimpleOutputer}; +use rustynotes::args::SimpleArgs; +use clap::{Parser, CommandFactory, FromArgMatches}; + +fn main() { + const CHANNELS: u32 = 2; + const PERIOD_SIZE: usize = 800; + const PROC_SIZE: usize = 5600; //1 << 13; // 8k, 16k, 32k + //let disp_threshold: f32 = 0.07; + //let disp_t_str = "0.07"; + let disp_t_str = "0.00526532149076897"; + let sample_rate = 48000; + let notes = note_range((Notes::C, 2), (Notes::B, 10)); + let rfproc: RFftProcer<f32, PERIOD_SIZE, PROC_SIZE> = RFftProcer::new(sample_rate); + let pnotes = rfproc.make_pnotes(¬es); + println!("notes len: {}, pnotes len: {}", notes.len(), pnotes.len()); + for pnote in &pnotes { + println!("{}", pnote); + } + println!("{}", rfproc); + + let mut cmd = SimpleArgs::command(); + cmd = cmd.mut_arg("threshold", |ta| ta.default_value(disp_t_str)); + let mut matches = cmd.get_matches(); + let args = SimpleArgs::from_arg_matches_mut(&mut matches).unwrap(); + let disp_threshold = args.threshold; + println!("Args: {:?}", args); + + let rfpdata = ProcerData::new(&rfproc, ProcerNotes(pnotes, disp_threshold)); + let outputer = args.get_outputer(¬es); + let mut buf: StaticBuffer<f32, PERIOD_SIZE, PROC_SIZE> = StaticBuffer::new(48000, CHANNELS, + vec![(Procers::Rfft(rfproc), rfpdata)], "mpd_c_snoop".to_string(), args.get_outdev(), + outputer); + println!("{}", buf); + let mut aout = alsa::Output::buffer_open().unwrap(); + buf.adev.dump(&mut aout); + //buf.outdev.dump(&mut aout); + match &buf.outdev { + Some(outdev) => { outdev.dump(&mut aout); }, + None => {}, + } + println!("{}", aout); + buf.capture_loop(); +} diff --git a/src/bin/smolguitar.rs b/src/bin/smolguitar.rs new file mode 100644 index 0000000..bb36b64 --- /dev/null +++ b/src/bin/smolguitar.rs @@ -0,0 +1,44 @@ +use rustynotes::notes::{Notes, note_range, ProcerNotes}; +use rustynotes::proc::{Procer, ProcerData}; +use rustynotes::rfft::RFftProcer; +use rustynotes::buf::{StaticBuffer, Procers}; +use rustynotes::outputers::{Outputers, SimpleOutputer, LineLayout}; +use rustynotes::args::SimpleArgs; +use clap::Parser; + +fn main() { + const CHANNELS: u32 = 1; + const PERIOD_SIZE: usize = 480; + const PROC_SIZE: usize = 1 << 13; // 8k + //let disp_threshold = 0.00372314453125; + //let disp_threshold = 0.00526532149076897; + //let disp_threshold = 0.05; + let sample_rate = 48000; + let notes = note_range((Notes::C, 2), (Notes::E, 7)); + let rfproc: RFftProcer<f32, PERIOD_SIZE, PROC_SIZE> = RFftProcer::new(sample_rate); + let pnotes = rfproc.make_pnotes(¬es); + for pnote in &pnotes { + println!("{}", pnote); + } + println!("{}", rfproc); + //let outputer = Outputers::Simple(SimpleOutputer); + //(2f32).sqrt() + //let outputer = Outputers::LineLayout(LineLayout::new(0.25, true, (60., 255., 220.), ¬es)); + let args = SimpleArgs::parse(); + let disp_threshold = args.threshold; + let outputer = args.get_outputer(¬es); + let rfpdata = ProcerData::new(&rfproc, ProcerNotes(pnotes, disp_threshold)); + let mut buf: StaticBuffer<f32, PERIOD_SIZE, PROC_SIZE> = StaticBuffer::new(48000, CHANNELS, + vec![(Procers::Rfft(rfproc), rfpdata)], "guitar_c".to_string(), args.get_outdev(), + outputer); + println!("{}", buf); + let mut aout = alsa::Output::buffer_open().unwrap(); + buf.adev.dump(&mut aout); + //buf.outdev.dump(&mut aout); + match &buf.outdev { + Some(outdev) => { outdev.dump(&mut aout); }, + None => {}, + } + println!("{}", aout); + buf.capture_loop(); +} diff --git a/src/bin/test_serde.rs b/src/bin/test_serde.rs new file mode 100644 index 0000000..02b42e4 --- /dev/null +++ b/src/bin/test_serde.rs @@ -0,0 +1,22 @@ +use crossterm::style::Color; +use serde::{Serialize, Deserialize}; +use serde_json::{to_string_pretty, from_str}; + +#[derive(Serialize, Deserialize, Debug)] +struct cscheme { + a: Color, + asharp: Color, + b: Color, +} + +fn main() { + let val = Color::Rgb {r: 110, g: 255, b: 220}; + let val2 = Color::AnsiValue(51); + let val3 = Color::DarkMagenta; + let scheme = cscheme {a: val, asharp: val2, b: val3}; + let json = to_string_pretty(&scheme).unwrap(); + println!("{}", json); + let result: cscheme = from_str(&json).unwrap(); + println!("{:?}", result); + //println!("{} {}", to_string_pretty +} diff --git a/src/bin/testrfft.rs b/src/bin/testrfft.rs new file mode 100644 index 0000000..ee8c72f --- /dev/null +++ b/src/bin/testrfft.rs @@ -0,0 +1,20 @@ +use realfft::{RealFftPlanner, FftNum, RealToComplex}; + +fn main() { + let mut planner: RealFftPlanner<f32> = RealFftPlanner::new(); + //let rfft = planner.plan_fft_forward(4); + let rfft = planner.plan_fft_forward(40); + let mut scratch = rfft.make_scratch_vec(); + let mut out_data = rfft.make_output_vec(); + //let mut in_data = [1., 0.4, 0.3, -1.]; + let mut in_data = [1.; 40]; + //let mut in_data = [ + rfft.process_with_scratch(&mut in_data, &mut out_data, &mut scratch).unwrap(); + println!("{:?}", &out_data); + let scale = 1./40.; + for elem in out_data { + println!("{}", elem.norm_sqr().sqrt() * scale); + println!("{}", elem.l1_norm() * scale); + //println!("{}", elem * scale); + } +} diff --git a/src/buf.rs b/src/buf.rs new file mode 100644 index 0000000..fb4a6dd --- /dev/null +++ b/src/buf.rs @@ -0,0 +1,244 @@ +use crate::proc::{Procer, ProcerData, Update}; +use crate::notes::{Note, NoteValue, ProcerNote}; +use crate::outputers::{Outputer, Outputers, SimpleOutputer}; +use crate::rfft::RFftProcer; +use realfft::FftNum; +use rustfft::num_traits::float::Float; +use alsa::{Direction, ValueOr}; +use alsa::pcm::{PCM, HwParams, Format, Access, State}; +use derive_more::Display; +use std::fmt::Debug; +use std::io::Write; +//use std::collections::VecDeque; + +pub enum Procers<I: Default + Clone + FftNum + Float, const US: usize, const BS: usize> { + Rfft(RFftProcer<I, US, BS>), + // XXX: if i add a fir filter that doesn't need a buffer i think + // i can just spec it as Fir12Tet(Fir12TetProcer<I, US>) or similar + // here +} + +#[derive(Display)] +#[display(fmt="StaticBuffer(rate={}, psize={}, channels={}, opsize={}, adev=(name={}), outdev=(name={:?}), cperiods={}, operiods={})", rate, period_size, channels, out_period_size, adev_name, outdev_name, capture_periods, output_periods)] +pub struct StaticBuffer<'nl, I: Default + Debug + Clone + FftNum + Float + NoteValue, const US: usize, const BS: usize> { + pub rate: u32, + pub period_size: usize, + pub channels: u32, + pub out_period_size: usize, + pub procers: Vec<(Procers<I, US, BS>, ProcerData<'nl, I>)>, + pub adev_name: String, + pub adev: PCM, + pub outdev_name: Option<String>, + pub outdev: Option<PCM>, + pub outputer: Outputers, + pub capture_periods: u32, + pub output_periods: u32, +} + +impl<'nl, I: Default + Debug + Clone + FftNum + Float + From<i16> + NoteValue, const US: usize, const BS: usize> StaticBuffer<'nl, I, US, BS> { + pub fn new(rate: u32, channels: u32, procers: Vec<(Procers<I, US, BS>, ProcerData<'nl, I>)>, adev_name: String, outdev_name: Option<String>, outputer: Outputers) -> Self { + let mut adev = PCM::new(&adev_name, Direction::Capture, true).unwrap(); + let mut capture_periods = 0; + { + let hwp = HwParams::any(&adev).unwrap(); + hwp.set_channels(channels).unwrap(); + //hwp.set_rate_resample(false); + hwp.set_rate(rate, ValueOr::Nearest).unwrap(); + hwp.set_format(Format::s16()).unwrap(); + hwp.set_access(Access::MMapInterleaved).unwrap(); + hwp.set_period_size(US as i64, ValueOr::Nearest).unwrap(); + hwp.set_periods(8, ValueOr::Nearest).unwrap(); + // TODO: directly using the ALSA ring buffer and copying directly from it instead of + // going through the Update/VecDeque might be interesting in the future, i think it'd + // require changing the const sizes to runtime determined sizes though, so future + // changes to try~ + adev.hw_params(&hwp).unwrap(); + capture_periods = hwp.get_periods().unwrap(); + println!("capture periods: {}", capture_periods); + } + let (outdev, out_period_size, output_periods) = match &outdev_name { + None => (None, 0, 0), + Some(od_name) => { + let mut outdev = PCM::new(&od_name, Direction::Playback, true).unwrap(); + let mut output_periods = 0; + let mut out_period_size = 0; + { + let hwp = HwParams::any(&outdev).unwrap(); + hwp.set_channels(channels).unwrap(); + hwp.set_rate(rate, ValueOr::Nearest).unwrap(); + hwp.set_format(Format::s16()).unwrap(); + hwp.set_access(Access::RWInterleaved).unwrap(); + //hwp.set_period_size(US as i64, ValueOr::Nearest).unwrap(); + //hwp.set_periods(8, ValueOr::Nearest).unwrap(); + outdev.hw_params(&hwp).unwrap(); + output_periods = hwp.get_periods().unwrap(); + out_period_size = hwp.get_period_size().unwrap() as usize; + println!("output periods: {}", output_periods); + } + (Some(outdev), out_period_size, output_periods) + } + }; + return Self { + rate, procers, adev_name, adev, outdev_name, outdev, + period_size: US, out_period_size: out_period_size, + channels: channels, + outputer, + capture_periods, output_periods, + } + } + + pub fn capture_loop(&mut self) { + let empty: Vec<i16> = vec![0i16; US * self.channels as usize]; + //let mut line_str: String = format!("\r{:80}", ""); + let in_io = self.adev.io_i16().unwrap(); + let out_io_opt = { + match &self.outdev { + None => (None), + Some(outdev) => { + //self.adev.link(&outdev).unwrap(); + let out_io = outdev.io_i16().unwrap(); + // queue up an empty period in the outdev so we can sync between in and out devices + // with a 4 period latency + for _i in 0..std::cmp::min(4, self.output_periods/2) { + out_io.writei(&empty).unwrap(); + } + outdev.start();//.unwrap(); + println!("output available: {}", outdev.avail().unwrap()); + Some(out_io) + } + } + }; + self.adev.start();//.unwrap(); + //let vt: I = I::from(112); + let conv_scale: I = <I as From<i16>>::from(i16::MAX); + println!("input available: {}", self.adev.avail_update().unwrap()); + let mut latentframes = 0; + let mut unstarted = false; + loop { + /*println!("output available: {}", self.outdev.avail().unwrap()); + println!("input available: {}", self.adev.avail_update().unwrap());*/ + self.adev.wait(None).unwrap(); + //println!("a"); + // should be called right before mmap_begin according to: + // https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___direct.html#ga6d4acf42de554d4d1177fb035d484ea4 + let avail_frames = self.adev.avail_update().unwrap(); + in_io.mmap(US, |in_slice| { + let size = in_slice.len(); + //println!("slice length: {}", size); + if size == 0 { + println!("warning: passed a 0-sized buffer from mmap\n"); + return 0; + } + let stride = self.channels as usize; + if size != (US * stride) { + println!("warning: passed a {} sized buffer from mmap\n\n", size); + return 0; + } + assert_eq!(size, US * stride); + let (written, oa) = match &out_io_opt { + Some(out_io) => { + let wri = out_io.writei(&in_slice).unwrap(); + assert_eq!(wri, (size/stride)); + let oaa = self.outdev.as_ref().unwrap().avail().unwrap(); + (wri, oaa) + } + None => (size/stride, 0i64), + }; + let ia = self.adev.avail_update().unwrap(); + let total_outbuf: i64 = oa - ia; + /*print!("\rtotal: {}, out: {}, in: {}", total_outbuf, oa, ia); + std::io::stdout().flush().unwrap();*/ + //return written; + //let constrained_slice = in_slice[0..US*stride]; + let mut update: Update<I, US> = [I::default(); US]; + // copy over the left channel data (even indexes) + // TODO/XXX: see if this uses SIMD or needs optimizing :o + // using iter and map/function that does strides would prob also work well :o + // gotta scale this from i16::MAX to float, alsa + // seems to divide by i16::MAX, not sure if there's a better way to do this + for i in 0..US { + update[i] = <I as From<i16>>::from(in_slice[i*stride]) / conv_scale; + }; + // hmm, i only need to use 1 procer here for displaying, not sure what to do with + // the rest :o + //for (procer, pdata) in self.procers { } + let (ref mut procer, ref mut pdata) = self.procers.first_mut().unwrap(); + let mut processed = false; + { + let mut pnotes = pdata.pnotes.0.as_mut_slice(); + //let processed = true; + processed = procer.process_data(&update, pnotes); + } + if processed { + self.outputer.handle_pnotes(&mut pdata.pnotes); + //let mut position = 1; + // TODO: maybe copy, inplace probably works for now though + /*pnotes.sort_unstable_by(|pn1, pn2| (-pn1.2).partial_cmp(&-pn2.2).unwrap()); + print!("\r{:-80}", pdata.pnotes); + std::io::stdout().flush();*/ + } + //let mut position = 0; + //let mut remaining = in_slice.len(); + //let mut remain_frames = remaining/(self.channels as usize); + //let mut handled = false; + //self.outdev.wait(None).unwrap(); + //println!("wrote {} frames", written); + /*while handled == false { + self.outdev.wait(None).unwrap(); + out_io.mmap(remain_frames, |out_slice| { + let outlen = out_slice.len(); + println!("out slice len: {}", out_slice.len()); + if outlen == 0 { + return 0; + } + out_slice.clone_from_slice(&in_slice); + handled = true; + return out_slice.len()/(self.channels as usize); + }).unwrap(); + }*/ + /*if latentframes > 0 { + latentframes -= 1; + } else if unstarted { + unstarted = false; + println!("starting outdev"); + self.outdev.start().unwrap(); + }*/ + return in_slice.len()/(self.channels as usize); + }).unwrap(); + } + } + //fn new( +} + +/*impl StaticBuffer<'nl, B> { +}*/ + +//impl<'nl, B> StaticBuffer<'nl, A> { + //pub fn fill_initial(&self, + //pub fn new(rate: usize, period_size: usize, out_period_size: usize, procers: Vec<(Procers<f64>, ProcerData<'nl>)>, +//} + + +impl<I: Default + Clone + FftNum + Float + NoteValue, const US: usize, const BS: usize> Procer<I, US> for Procers<I, US, BS> { + fn get_size(&self) -> usize { + match self { + Procers::Rfft(rfp) => rfp.get_size(), + } + } + fn get_frequency(&self) -> usize { + match self { + Procers::Rfft(rfp) => rfp.get_frequency(), + } + } + fn make_pnotes<'nl>(&self, notes: &'nl [Note]) -> Vec<ProcerNote<'nl, I>> { + match self { + Procers::Rfft(rfp) => rfp.make_pnotes(notes), + } + } + fn process_data(&mut self, input: &Update<I, US>, notes: &mut [ProcerNote<I>]) -> bool { + match self { + Procers::Rfft(rfp) => rfp.process_data(input, notes), + } + } +} + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3a1eb7d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +pub mod notes; +pub mod proc; +pub mod outputers; +pub mod buf; +pub mod rfft; +pub mod args; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..022b6ce --- /dev/null +++ b/src/main.rs @@ -0,0 +1,66 @@ +mod notes; +mod proc; +mod outputers; +mod buf; +mod rfft; +mod args; + +use crate::notes::{Notes, note_range, ProcerNotes}; +use crate::proc::{Procer, ProcerData}; +use crate::rfft::RFftProcer; +use crate::buf::{StaticBuffer, Procers}; +use crate::outputers::{Outputers, SimpleOutputer, LineLayout}; +use crate::args::SimpleArgs; +use clap::{Parser, CommandFactory, FromArgMatches}; + +//use std::collections::VecDeque; + +fn main() { + const CHANNELS: u32 = 2; + const PERIOD_SIZE: usize = 240; // 1024 frames + const PROC_SIZE: usize = 1 << 14; // 16k frames (so about .3413_ of a second) + //let disp_threshold = 0.00372314453125; + //let disp_threshold = 0.00526532149076897; + //let disp_threshold = 50. / (i16::MAX as f32); + //const PERIOD_SIZE: usize = 480; + //const PROC_SIZE: usize = 1 << 13; // 8k + //let period_size = 1024; + let sample_rate = 48000; + //let proc_size = 1 << 14; // 16k (so about .3413_ of a second) + // we update every period_size so it updates every 21.3_ms but we keep a running average of the + // last 341.3_ms so low frequencies can be interpreted with the fft (using fir or iir filters + // or wavelets or inner product spaces and frames is something to do in the future, but for now i'm just + // porting older python code over for performance that used ffts, and maybe messing with dct + // but it'll have the same limitations of ffts mostly probably) + let notes = note_range((Notes::C, 2), (Notes::E, 7)); + // US and prob BS here should be *2 when dealing with stereo, but going with mono rn + let rfproc: RFftProcer<f32, PERIOD_SIZE, PROC_SIZE> = RFftProcer::new(sample_rate); + let pnotes = rfproc.make_pnotes(¬es); + //println!("{:#?}", pnotes); + for pnote in &pnotes { + println!("{}", pnote); + } + println!("{}", rfproc); + let args = SimpleArgs::parse(); + println!("Args: {:?}", args); + // TODO: having an app-specific default threshold might be a good idea for smolguitar, + // not sure how to do that yet, maybe there's an API for changing the default of an arg in clap + let disp_threshold = args.threshold; + //let outputer = Outputers::Simple(SimpleOutputer); + //let outputer = Outputers::LineLayout(LineLayout::new(0.2, true, (0., 255., 220.), ¬es)); + let outputer = args.get_outputer(¬es); + let rfpdata = ProcerData::new(&rfproc, ProcerNotes(pnotes, disp_threshold)); + let mut buf: StaticBuffer<f32, PERIOD_SIZE, PROC_SIZE> = StaticBuffer::new(48000, CHANNELS, + vec![(Procers::Rfft(rfproc), rfpdata)], "Microphone_c".to_string(), args.get_outdev(), + outputer); + println!("{}", buf); + let mut aout = alsa::Output::buffer_open().unwrap(); + buf.adev.dump(&mut aout); + match &buf.outdev { + Some(outdev) => { outdev.dump(&mut aout); }, + None => {}, + } + println!("{}", aout); + buf.capture_loop(); + //println!("{:#?}", pdata); +} diff --git a/src/notes.rs b/src/notes.rs new file mode 100644 index 0000000..41eb45e --- /dev/null +++ b/src/notes.rs @@ -0,0 +1,168 @@ +use derive_more::Display; +use std::fmt::{Debug, Write}; +use num_traits::{NumOps, ToPrimitive}; + +#[derive(Debug, Clone, Display, Copy, PartialEq)] +pub enum Notes { + #[display(fmt=" c")] + C, + #[display(fmt="c#")] + Cs, + #[display(fmt=" d")] + D, + #[display(fmt="d#")] + Ds, + #[display(fmt=" e")] + E, + #[display(fmt=" f")] + F, + #[display(fmt="f#")] + Fs, + #[display(fmt=" g")] + G, + #[display(fmt="g#")] + Gs, + #[display(fmt=" a")] + A, + #[display(fmt="a#")] + As, + #[display(fmt=" b")] + B, +} + +pub type BaseNote = (Notes, f64); + +const MIDDLE_A_VAL: f64 = 440.; +//const MIDDLE_A_LOWER: f64 = MIDDLE_A_VAL * (2f64).powf(-0.5f64 / 12f64); +//const MIDDLE_A_UPPER: f64 = MIDDLE_A_VAL * (2f64).powf(0.5f64 / 12f64); + +pub fn calc_freq_from_offset(offset: f64) -> f64 { + return MIDDLE_A_VAL * (2f64).powf(offset / 12f64); +} + +pub const NOTE_MA_OFFSETS: [BaseNote; 12] = [ + (Notes::C, -57.), (Notes::Cs, -56.), + (Notes::D, -55.), (Notes::Ds, -54.), (Notes::E, -53.), + (Notes::F, -52.), (Notes::Fs, -51.), + (Notes::G, -50.), (Notes::Gs, -49.), + (Notes::A, -48.), (Notes::As, -47.), + (Notes::B, -46.), +]; + +/*const NOTE_ORDER: [BaseNote; 12] = [ + (Notes::C, 16.352), (Notes::Cs, 17.324), + (Notes::D, 18.354), (Notes::Ds, 19.445), (Notes::E, 20.602), + (Notes::F, 21.827), (Notes::Fs, 23.125), + (Notes::G, 24.5), (Notes::Gs, 25.957), + (Notes::A, 27.5), (Notes::As, 29.135), + (Notes::B, 30.868) +];*/ + +#[derive(Debug, Display)] +#[display(fmt="{}{}", "note.0", octave)] +pub struct Note { + pub note: BaseNote, + pub low_freq: f64, + pub freq: f64, + pub high_freq: f64, + pub octave: i32, + //value: f64, +} + +pub trait NoteValue: Debug + PartialOrd + From<f32> + ToPrimitive + std::fmt::Display + Copy + NumOps {} +//where <Self as std::ops::Mul>::Output: std::fmt::Display {} + +//impl NoteValue for f32 {} +//impl NoteValue for f64 {} +impl<T> NoteValue for T +where T: Debug + PartialOrd + From<f32> + ToPrimitive + std::fmt::Display + Copy + NumOps {} + +// elements here are (Note, fft/proc data range for calculating the value, summed value, max value +// of each element in the bin) +#[derive(Debug, Display)] +//#[display(fmt="{}({:?}) {}", _0, _1 ,_2)] +//#[display(bound="I: From<f32> + std::ops::Mul + std::fmt::Display")] +#[display(fmt="{} {:.0} ", _0, "*_3*I::from(10000.0)")] +pub struct ProcerNote<'nl, I: NoteValue>(pub &'nl Note, pub core::ops::Range<usize>, pub I, pub I); +// .0 here is the notes data, .1 is the threshold for printing~ +#[derive(Debug)] +pub struct ProcerNotes<'nl, I: NoteValue>(pub Vec<ProcerNote<'nl, I>>, pub I); + +impl<'nl, I: NoteValue> std::fmt::Display for ProcerNotes<'nl, I> { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + //let threshold: I = I::from(0.00372314453125f32); + let threshold = self.1; + let mut tmp: String = String::with_capacity(1024); + for pn in self.0.iter().filter(|pn| pn.3 >= threshold) { + write!(tmp, "{pn}")?; + }; + fmt.pad(&tmp); + Ok(()) + } +} + + +/*impl<'nl, I: Debug + PartialOrd + From<f32>> Debug for ProcerNotes<'nl, I> { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + let threshold: I = I::from(122.0f32); + fmt.debug_list().entries(self.0.iter().filter(|pn| pn.2 > threshold)).finish() + } +}*/ + +pub trait Octavable { + fn get_octave(&self, octave: i32) -> Note; +} + +impl Octavable for BaseNote { + fn get_octave(&self, octave: i32) -> Note { + let offset = self.1 + ((12i32 * octave) as f64); + let low_offset = offset - 0.5; + let high_offset = offset + 0.5; + match octave { + 0 => Note { + note: *self, + low_freq: calc_freq_from_offset(self.1 - 0.5), + freq: calc_freq_from_offset(self.1), + high_freq: calc_freq_from_offset(self.1 + 0.5), + octave: 0//, value: 0. + }, + _ => Note { + note: *self, + low_freq: calc_freq_from_offset(low_offset), + freq: calc_freq_from_offset(offset), + high_freq: calc_freq_from_offset(high_offset), + octave: octave//, value: 0. + } + } + } +} + +// start and end should be tuples of (Note, octave) +pub fn note_range(start: (Notes, i32), end: (Notes, i32)) -> Vec<Note> { + let mut ret = Vec::with_capacity((end.1+3 - start.1) as usize *12); + let mut started: bool = false; + let start_note = start.0; + let (end_note, mut end_octave) = end; + end_octave += 2; + for octave in (start.1)..=end_octave { + for bnote in NOTE_MA_OFFSETS { + match (started, bnote.0, octave) { + (false, cur_note, _) if cur_note == start_note => { started = true; ret.push(bnote.get_octave(octave)) }, + (true, cur_note, _) if cur_note == end_note && octave == end_octave => { ret.push(bnote.get_octave(octave)); return ret; }, + (true, _, _) => { ret.push(bnote.get_octave(octave)) }, + (_, _, _) => {}, + } + } + } + return ret; +} + +/*impl Octavable for Note { + fn get_octave(&self, octave: i32) -> Self { + match octave { + 0 => Note {freq: self.note.1, octave: 0, value: 0., ..*self}, + _ => Note {freq: self.note.1 * ((1 << octave) as f64), octave: octave, value: 0., ..*self} + } + } +}*/ + diff --git a/src/outputers.rs b/src/outputers.rs new file mode 100644 index 0000000..4c56e56 --- /dev/null +++ b/src/outputers.rs @@ -0,0 +1,266 @@ +use crate::notes::{Note, BaseNote, Notes, NoteValue, ProcerNote, ProcerNotes}; +use std::collections::VecDeque; +use std::io::Write; +use std::fmt::{Debug}; +use realfft::FftNum; +//use clap::{Args,Subcommand}; + +//use std::io; +use crossterm::terminal; +use crossterm::style::{Stylize, Color}; +use tui::{backend::CrosstermBackend, Terminal}; +use serde::{Serialize, Deserialize}; +use ansi_colours::rgb_from_ansi256; + + +trait ToRgbVal { + fn to_rgb_value(&self) -> (u8, u8, u8); +} + +impl ToRgbVal for Color { + fn to_rgb_value(&self) -> (u8, u8, u8) { + match self { + Color::Rgb {r, g, b} => (*r, *g, *b), + // I'll use the tango colours for the colour constants + // but the Ansi lookup table for the Ansi versions + Color::Reset => (0, 0, 0), + Color::Black => (0, 0, 0), + Color::DarkRed => (204, 0, 0), + Color::DarkGreen => (78, 154, 6), + Color::DarkYellow => (196, 160, 0), + Color::DarkBlue => (54, 101, 164), + Color::DarkMagenta => (117, 80, 123), + Color::DarkCyan => (6, 152, 154), + Color::Grey => (211, 215, 207), + Color::DarkGrey => (85, 87, 83), + Color::Red => (239, 41, 41), + Color::Green => (138, 226, 52), + Color::Yellow => (252, 233, 79), + Color::Blue => (114, 159, 207), + Color::Magenta => (173, 127, 168), + Color::Cyan => (52, 226, 226), + Color::White => (238, 238, 236), + Color::AnsiValue(ansi) => rgb_from_ansi256(*ansi), + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct ColourScheme { + pub a: Color, pub as_: Color, + pub b: Color, + pub c: Color, pub cs: Color, + pub d: Color, pub ds: Color, + pub e: Color, + pub f: Color, pub fs: Color, + pub g: Color, pub gs: Color, +} + +impl ColourScheme { + fn get_colour(&self, note: Notes) -> Color { + match note { + Notes::C => self.c, + Notes::Cs => self.cs, + Notes::D => self.d, + Notes::Ds => self.ds, + Notes::E => self.e, + Notes::F => self.f, + Notes::Fs => self.fs, + Notes::G => self.g, + Notes::Gs => self.gs, + Notes::A => self.a, + Notes::As => self.as_, + Notes::B => self.b, + } + } +} + +impl Default for ColourScheme { + fn default() -> Self { + let c = Color::Rgb {r: 140, g: 255, b: 210}; + return Self { + a: c, as_: c, + b: c, + c: c, cs: c, + d: c, ds: c, + e: c, + f: c, fs: c, + g: c, gs: c, + } + } +} + +pub trait Outputer<I: FftNum + NoteValue> { + fn setup(&mut self); + fn handle_pnotes(&mut self, notes: &mut ProcerNotes<I>); +} + +pub enum Outputers { + Simple(SimpleOutputer), + LineLayout(LineLayout), + Tui(TuiOutputer), +} + +pub struct SimpleOutputer; +pub struct LineLayout { + term_width: usize, + notes_per_line: usize, + last_line: String, + empty: String, + last_was_empty: bool, + use_colour: bool, + //max_c: (f32, f32, f32), + colours: ColourScheme, + max_threshold: f32, + //threshold: f32, +} +pub struct TuiOutputer { + terminal: Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>, +} + +impl<I: FftNum + NoteValue> Outputer<I> for SimpleOutputer { + fn setup(&mut self) {} + fn handle_pnotes(&mut self, notes: &mut ProcerNotes<I>) { + notes.0.sort_unstable_by(|pn1, pn2| (-(pn1.3)).partial_cmp(&-(pn2.3)).unwrap()); + print!("\r{:-80}", notes); + std::io::stdout().flush().unwrap(); + } +} + +pub fn line_note_char(note: &Note) -> char { + match note.note.0 { + Notes::C => 'c', + Notes::Cs => match note.octave % 10 { + 0 => '0', + 1 => '1', + 2 => '2', + 3 => '3', + 4 => '4', + 5 => '5', + 6 => '6', + 7 => '7', + 8 => '8', + 9 => '9', + _ => ' ', + }, + Notes::D => 'd', + Notes::Ds => ' ', + Notes::E => 'e', + Notes::F => 'f', + Notes::Fs => ' ', + Notes::G => 'g', + Notes::Gs => ' ', + Notes::A => 'a', + Notes::As => ' ', + Notes::B => 'b' + } +} + +impl LineLayout { + pub fn new(max_threshold: f32, use_colour: bool, colours: ColourScheme, notes: &[Note]) -> Self { + let term_width = terminal::size().unwrap().0 as usize; + let note_count = std::cmp::min(notes.len(), term_width); + return Self { + term_width, use_colour, colours, max_threshold, notes_per_line: note_count, + last_line: notes[0..note_count].iter().map(line_note_char).collect(), + empty: " ".repeat(term_width), + last_was_empty: true, + } + } + + pub fn line_bw<I: FftNum + NoteValue>(&self, note_count: usize, notes: &ProcerNotes<I>) { + print!("\r"); + let itx: String = notes.0[0..note_count].iter().map(|note| { + if note.3 >= notes.1 { + '▉' + } else { + ' ' + } + }).collect(); + print!("{}\n{}", itx, self.last_line); + std::io::stdout().flush().unwrap(); + } + + pub fn line_colour<I: FftNum + NoteValue>(&self, note_count: usize, notes: &ProcerNotes<I>) { + print!("\r"); + // TODO: do grouping to avoid excessive control characters + // for now this just does the simplest thing though + for note in ¬es.0[0..note_count] { + if note.3 >= notes.1 { + let mut bright: f32 = note.3.to_f32().unwrap() / self.max_threshold; + if bright > 1.0 { + bright = 1.0; + } + let (r, g, b) = self.colours.get_colour(note.0.note.0).to_rgb_value(); + print!("{}", ' '.on(Color::Rgb { + r: (r as f32 * bright) as u8, + g: (g as f32 * bright) as u8, + b: (b as f32 * bright) as u8, + })); + } else { + print!(" "); + } + } + print!("\n{}", self.last_line); + std::io::stdout().flush().unwrap(); + } +} + +impl<I: FftNum + NoteValue> Outputer<I> for LineLayout { + fn setup(&mut self) {} + fn handle_pnotes(&mut self, notes: &mut ProcerNotes<I>) { + let notes_len = notes.0.len(); + let note_count = std::cmp::min(notes_len, self.notes_per_line); + if notes.0[0..note_count].iter().all(|note| note.3 < notes.1) { + if self.last_was_empty { + return + } else { + self.last_was_empty = true; + } + } else { + self.last_was_empty = false; + } + match self.use_colour { + true => self.line_colour(note_count, notes), + false => self.line_bw(note_count, notes) + } + } +} + +impl TuiOutputer { + pub fn new() -> Result<Self, std::io::Error> { + let stdout = std::io::stdout(); + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + return Ok(Self { + terminal, + }) + } +} + +impl<I: FftNum + NoteValue> Outputer<I> for TuiOutputer { + fn setup(&mut self) {} + fn handle_pnotes(&mut self, notes: &mut ProcerNotes<I>) { + //self.terminal.draw(|frame| {}) + } +} + + +impl<I: FftNum + NoteValue> Outputer<I> for Outputers { + fn setup(&mut self) { + match self { + Outputers::Simple(so) => <SimpleOutputer as Outputer<I>>::setup(so), + Outputers::LineLayout(llo) => <LineLayout as Outputer<I>>::setup(llo), + Outputers::Tui(to) => <TuiOutputer as Outputer<I>>::setup(to) + } + } + + fn handle_pnotes(&mut self, notes: &mut ProcerNotes<I>) { + match self { + Outputers::Simple(so) => so.handle_pnotes(notes), + Outputers::LineLayout(llo) => llo.handle_pnotes(notes), + Outputers::Tui(to) => to.handle_pnotes(notes), + } + } +} + diff --git a/src/proc.rs b/src/proc.rs new file mode 100644 index 0000000..2b32747 --- /dev/null +++ b/src/proc.rs @@ -0,0 +1,162 @@ +//#![feature(split_array)] +//#![feature(adt_const_params)] +//#![feature(generic_const_exprs)] +use crate::notes::{Note, NoteValue, ProcerNote, ProcerNotes}; +//use std::sync::Arc; +use std::collections::VecDeque; + +// Arc does heap allocate, so it might make sense to just pass owned arrays around and copy/clone +// when needed +//pub type Update<const S: usize> = Arc<[f64; S]>; +pub type Update<I, const S: usize> = [I; S]; +pub type UpDequer<I, const S: usize> = VecDeque<Update<I, S>>; + +pub trait Procer<I: NoteValue, const US: usize> { + fn get_size(&self) -> usize; + fn get_frequency(&self) -> usize; + fn make_pnotes<'nl>(&self, notes: &'nl [Note]) -> Vec<ProcerNote<'nl, I>>; + // this function only modifies ProcerNotes when you have enough data + // otherwise it leaves them alone or sets them to 0 (not sure which yet) + fn process_data(&mut self, input: &Update<I, US>, notes: &mut [ProcerNote<I>]) -> bool; +} + +pub trait DequerUtils<I, const BS: usize> { + fn cur_max_buffer_size(&self) -> usize; + fn update_buffer_array(&self, buf: &mut [I; BS]) -> bool; + //fn make_buffer(&self) -> Option<[I; BS]>; +} + +// the tuple this returns is (plan_size (ie the - index of the first element to copy from), +// Option<copy_size>), if the option is None copy the entire update, otherwise copy +// copy_size from the end of the first array +const fn make_buf_plan(buf_size: usize, update_size: usize) -> (usize, Option<usize>) { + let remainder_size = buf_size % update_size; + let plan_size = buf_size / update_size; + if remainder_size > 0 { + return (plan_size+1, Some(remainder_size)); + } else { + return (plan_size, None); + } +} + +impl<I: Clone, const S: usize, const BS: usize> DequerUtils<I, BS> for UpDequer<I, S> { + #[inline(always)] + fn cur_max_buffer_size(&self) -> usize { + S * self.len() + } + + fn update_buffer_array(&self, buf: &mut [I; BS]) -> bool { + let total_bufs = self.len(); + let max_size = S * total_bufs; + if BS > max_size { + return false; + } + // XXX: calculate this at runtime for now, const generics (atleast currently) can't be used + // for calculating consts + // <https://github.com/rust-lang/rfcs/blob/master/text/2000-const-generics.md#when-a-const-variable-can-be-used> + // TODO: this could be made const itself by making a consts mod and putting the update and + // buffer sizes as consts in there instead of having them as const generic arguments + let plan = make_buf_plan(BS, S); + //let mut cur_start = 0; + let mut cur_index = total_bufs - plan.0; + //println!("{:?}, {}, {}", plan, cur_index, S); + let (copy_s, slicey) = match plan.1 { + None => (S, self.get(cur_index).unwrap().as_slice()), + Some(copy_size) => (copy_size, &self.get(cur_index).unwrap()[(S-copy_size)..]) + }; + //println!("{:?}", slicey); + // do the copy n stuff + let (mut left, mut right) = buf.split_at_mut(copy_s); + left.clone_from_slice(slicey); + //cur_start += copy_s; + cur_index += 1; + for up_i in cur_index..total_bufs { + //let (mut l2, r2) = right.split_at_mut(S); + //right = r2; + (left, right) = right.split_at_mut(S); + left.clone_from_slice(self.get(up_i).unwrap().as_slice()); + } + return true; + } + + /*fn make_buffer_vec(&self) -> Option<[I; BS]> { + let total_bufs = self.len(); + let max_size = S * total_bufs; + if BS > max_size { + return None; + } + // convert this bit to a const fn, because it can be :o, could maybe make a const + // copy_bytes_from_iter function or something + //const remainder_size: usize = BS / S; + //const S_2: usize = S; + //const PLAN: (usize, Option<usize>) = make_buf_plan(BS_2, S_2); + /*const PARTIAL_FIRST: bool = PLAN.1.is_some(); + let mut cur_start = 0; + let mut cur_index = total_bufs - PLAN.0; + let mut ret: [f64; BS] = [0.0; BS]; + if PARTIAL_FIRST { + const Some(PVAL): usize = PARTIAL_FIRST; + let slicey = self.get(cur_index).rsplit_array_ref(copy_size); + let (left, _) = ret.split_array_mut::<PVAL>(); + left.clone_from(slicey); + }*/ + /*let slicey = match PLAN.1 { + None => &self.get(cur_index) + Some(copy_size) => &self.get(cur_index).rsplit_array_ref(copy_size) + }*/ + let mut ret: [I; BS] = [I::default(); BS]; + return Some(ret); + }*/ +} + +#[derive(Debug)] +pub struct ProcerData<'nl, I: NoteValue> { + pub pnotes: ProcerNotes<'nl, I>, + size: usize, + frequency: usize, + pub current: bool, +} + +impl<'nl, I: NoteValue> ProcerData<'nl, I> { + pub fn new<const US: usize>(procer: &impl Procer<I, US>, pnotes: ProcerNotes<'nl, I>) -> Self { + return Self { + pnotes: pnotes, + size: procer.get_size(), + frequency: procer.get_frequency(), + current: false, + } + } +} + +#[cfg(test)] +mod tests { + use crate::proc::{Update, UpDequer, DequerUtils}; + use std::collections::VecDeque; + #[test] + fn test_update_buffer_array() { + let mut data_dq: UpDequer<f32, 5> = VecDeque::with_capacity(6); + let updates: [Update<f32, 5>; 5] = [[1.0, 2.0, 3.0, 4.0, 5.0], [6.0, 7.0, 8.0, 9.0, 10.0], [11.0, 12.0, 13.0, 14.0, 15.0], [16.0, 17.0, 18.0, 19.0, 20.0], [21.0, 22.0, 23.0, 24.0, 25.0]]; + data_dq.push_back(updates[0]); + data_dq.push_back(updates[1]); + data_dq.push_back(updates[2]); + data_dq.push_back(updates[3]); + data_dq.push_back(updates[4]); + let mut ba: [f32; 24] = [0.0; 24]; + let copied = data_dq.update_buffer_array(&mut ba); + assert_eq!(copied, true); + assert_eq!(ba, [2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0]); + let mut small_ba: [f32; 3] = [0.0; 3]; + let copied2 = data_dq.update_buffer_array(&mut small_ba); + assert_eq!(copied2, true); + assert_eq!(small_ba, [23.0, 24.0, 25.0]); + let mut full_ba: [f32; 25] = [0.0; 25]; + let copied3 = data_dq.update_buffer_array(&mut full_ba); + assert_eq!(copied2, true); + assert_eq!(full_ba, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0]); + let mut toobig: [f32; 26] = [0.0; 26]; + let copied4 = data_dq.update_buffer_array(&mut toobig); + assert_eq!(copied4, false); + assert_eq!(toobig, [0.0; 26]); + } +} + diff --git a/src/rfft.rs b/src/rfft.rs new file mode 100644 index 0000000..8e5fc0f --- /dev/null +++ b/src/rfft.rs @@ -0,0 +1,179 @@ +use crate::proc::{Procer, UpDequer, DequerUtils, Update}; +use crate::notes::{Note, NoteValue, ProcerNote}; +use core::ops::Range; +//use std::collections::VecDeque; +use std::sync::Arc; + +use realfft::{RealFftPlanner, FftNum, RealToComplex}; +use rustfft::num_complex::Complex; +//use rustfft::num_traits::Zero; +use rustfft::num_traits::One; +use rustfft::num_traits::float::Float; +use derive_more::Display; +use std::fmt::Debug; + + +trait DebugableRTC<I>: RealToComplex<I> + Debug {} + + +// the Arc<dyn> here really increases compile times, but that might just be because it has to link +// in realfft/rustfft where before it didn't get linked in + +#[derive(Display)] +#[display(fmt="RfftProcer(size={}, outsize={}, freq={}, deque=(size={}, cap={}), in_buf_size={}, scratch=(size={}, cap={}) out_data=(size={}, cap={}))", size, outsize, frequency, "in_deque.len()", "in_deque.capacity()", "in_buf.len()", "scratch.len()", "scratch.capacity()", "out_data.len()", "out_data.capacity()")] +pub struct RFftProcer<I: Clone + FftNum, const US: usize, const BS: usize> { + pub size: usize, + pub outsize: usize, + pub frequency: usize, + pub in_deque: UpDequer<I, US>, + pub in_buf: [I; BS], + pub rfft: Arc<dyn RealToComplex<I>>, + // TODO: could make this a fixed array instead of a vec too maybe + pub scratch: Vec<Complex<I>>, + pub out_data: Vec<Complex<I>>, + pub scale: I, + //pub in_deque: VecDeque<Update<A>>, +} + +impl<I: Default + Copy + FftNum + Float, const US: usize, const BS: usize> RFftProcer<I, US, BS> { + pub fn new(frequency: usize) -> Self { + let retsize = BS/2+1; + // TODO: could do some cleverness with using double then you'd only need to pop elements + // off after you're double-full, but i think just having a buffer of 1 or 2 elements is + // easier here and popping before each add in process_data if we're full + let dq_capacity = (BS/US)+2; + assert!((dq_capacity * US) > BS); + let mut planner = RealFftPlanner::new(); + let rfft = planner.plan_fft_forward(BS); + let scratch = rfft.make_scratch_vec(); + let out_data = rfft.make_output_vec(); + return Self { + size: BS, + outsize: retsize, + frequency: frequency, + in_deque: UpDequer::with_capacity(dq_capacity), + in_buf: [I::default(); BS], + rfft: rfft, + //out_data: Vec::with_capacity(retsize), + scratch: scratch, + out_data: out_data, + // the docs say to scale by 1/sqrt(len), but it seems like 1/len (where len is the real + // input size) is the correct scaling to get results with norm_sqrt results between 0-1 + // I'm still not entirely sure about this though, l1_norm also seems to max out at len + // without scaling so 1/len i think is right for it too, but it also gives lower + // results when there's an imaginary part + //scale: /*I::from_usize(10).unwrap()*/I::one() / I::from_usize(retsize/*-1*/).unwrap().sqrt(), + scale: I::one() / I::from_usize(BS).unwrap(), + } + } +} + +impl<I: Default + Clone + FftNum + Float + NoteValue, const US: usize, const BS: usize> Procer<I, US> for RFftProcer<I, US, BS> { + fn get_size(&self) -> usize { + self.size + } + fn get_frequency(&self) -> usize { + self.frequency + } + + fn make_pnotes<'nl>(&self, notes: &'nl [Note]) -> Vec<ProcerNote<'nl, I>> { + // TODO: +1 or nah? (might have to handle it differently for even or odd, hopefully not + // though) + //let retsize = self.size/2+1; + let retsize = self.outsize; + let noteslen = notes.len(); + let mut ret: Vec<ProcerNote<'nl, I>> = Vec::with_capacity(noteslen); + let mut cur_note_idx = 0; + let mut cutoff = notes[0].high_freq; + let scale: f64 = (self.frequency as f64)/(self.size as f64); + // TODO: since we do have .low_freq here as well you can start the low size of the range + // close to .low_freq (how close?) but for now we'll start it at 1 + let mut cur_range: Range<usize> = 1..1; + for i in 1..retsize { + let freq: f64 = (i as f64) * scale; + if freq > cutoff { + while freq > cutoff { + let pnote = ProcerNote::<'nl, I>(¬es[cur_note_idx], cur_range.clone(), I::default(), I::default()); + ret.push(pnote); + cur_range.start = cur_range.end; + // worth noting this is different than the python code, + // here frequencies above the high note aren't added in + // where the python code adds them all to the highest + // note, you change that by changing the return below + // here to a cutoff = <however you express infinite> i + // think + cur_note_idx += 1; + if cur_note_idx >= noteslen { + return ret; + } + cutoff = notes[cur_note_idx].high_freq; + } + } + //else { + cur_range.end += 1; + //} + } + // add the final pnote if we ran out of frequencies before running + // out of notes + ret.push(ProcerNote::<'nl, I>(¬es[cur_note_idx], cur_range, I::default(), I::default())); + return ret; + } + + fn process_data(&mut self, input: &Update<I, US>, notes: &mut [ProcerNote<I>]) -> bool { + let dq_len = self.in_deque.len(); + let dq_cap = self.in_deque.capacity(); + // we should probably make sure buf_size can be made with the length of dq as well though + // so capacity can grow if new() messed up with the capacity + if dq_len == dq_cap { + self.in_deque.pop_front(); + } + let scale = self.scale; + // XXX: mm this copies twice per update (once to add it to the deque and once to the buffer) + // which i kinda wanted to avoid (using an Arc would avoid it but it'd be extra heap + // allocations) atleast it doesn't realloc though so hopefully that's ok + // depending on how this works with Alsa i might wanna switch to passing in a slice and + // storing references in the UpDeque but that makes lifetime soup + // or something else maybes + // OHH though since i'm probably getting i16 from alsa and want to convert to f32 anyway it + // makes sense to copy (question is will that add a third copy or will the compiler be + // smart enough to convert inplace, i think ideally the conversion would happen here in + // this push_back actually, maybe the input Update should be AI and this push_back should + // be as I or Into or whatever) + self.in_deque.push_back(input.clone()); + let updated = self.in_deque.update_buffer_array(&mut self.in_buf); + //println!("updated = {}", updated); + if updated { + //assert_eq!(self.in_buf, [I::default(); BS]); + let post_scale = I::from_u8(2).unwrap().sqrt(); + self.rfft.process_with_scratch(&mut self.in_buf, &mut self.out_data, &mut self.scratch).unwrap(); + for pnote in notes { + // TODO: figure out the best calculation for this + // TODO: map/sum or product might be quicker then fold here, since fold can't + // really use simd, also look at what the perftest code did to use simd + // XXX: python's abs uses the equiv of sqrt(norm_sqr) (https://docs.rs/num-complex/0.4.3/num_complex/struct.Complex.html#method.norm_sqr) + // ie. sqrt(|real|**2 + |imag|**2), gonna just use norm_sqrt here for now + // l1_norm might also be a good option (just |real| + |imag|), and gets rid of the + // pow too, but i dunno what's best here + // TODO: the python test2.py code actually currently uses max instead of folding here too + //let complexes = self.out_data[pnote.1.clone()].iter().fold((I::default(), Complex::<I>::default()), |(sum, mx), c| {let c_sqrt = c.scale(scale).norm_sqr().sqrt(); (sum + c_sqrt, std::cmp::max_by(mx, *c, |a: &Complex<I>, b: &Complex<I>| { a.norm_sqr().partial_cmp(&b.norm_sqr()).unwrap() }))}); + let complexes = self.out_data[pnote.1.clone()].iter().fold((I::default(), Complex::<I>::default(), I::default()), |(sum, mx_c, mx_sqrt), c| { + let c_sqr = c.scale(scale).norm_sqr(); + let c_sqrt = c_sqr.sqrt(); + if c_sqrt > mx_sqrt { + (sum + c_sqr, *c, c_sqrt) + } else { + (sum + c_sqr, mx_c, mx_sqrt) + } + }); + pnote.2 = complexes.0.sqrt() * post_scale; // / I::from_usize(pnote.1.len()).unwrap(); + //pnote.3 = complexes.1.scale(scale).norm_sqr() * I::from_f32(2.0).unwrap(); + //pnote.2 = complexes.0.scale(scale).norm_sqr().sqrt(); + //pnote.3 = complexes.1.scale(scale).norm_sqr().sqrt(); + pnote.3 = complexes.2 * post_scale; + //pnote.2 = self.out_data[pnote.1.clone()].iter().fold(I::default(), |acc, c| acc + (c.norm_sqr().sqrt()*scale))/*.sqrt()*/; + //pnote.2 = self.out_data[pnote.1.clone()].iter().map(|c| c.norm_sqr()).max_by(|a, b| { a.partial_cmp(b).unwrap() } ); + } + } + return updated; + } +} |