├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── demo.gif ├── readme.md └── src ├── audio.rs ├── audio ├── pod_choice_default.rs └── spa_audio_info_raw.rs ├── graphics.rs ├── graphics ├── device.rs ├── pipeline.rs ├── sampler.rs ├── surface.rs ├── swapchain.rs └── vertex.rs ├── main.rs ├── shaders ├── basic.frag ├── basic.vert ├── frag.spv └── vert.spv ├── visualiser.rs └── window.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" 10 | dependencies = [ 11 | "cfg-if", 12 | "getrandom", 13 | "once_cell", 14 | "version_check", 15 | ] 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "0.7.20" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "anyhow" 28 | version = "1.0.69" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" 31 | 32 | [[package]] 33 | name = "apodize" 34 | version = "1.0.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "fca387cdc0a1f9c7a7c26556d584aa2d07fc529843082e4861003cde4ab914ed" 37 | 38 | [[package]] 39 | name = "ash" 40 | version = "0.37.2+1.3.238" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "28bf19c1f0a470be5fbf7522a308a05df06610252c5bcf5143e1b23f629a9a03" 43 | dependencies = [ 44 | "libloading", 45 | ] 46 | 47 | [[package]] 48 | name = "assert_float_eq" 49 | version = "1.1.3" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "4cea652ffbedecf29e9cd41bb4c066881057a42c0c119040f022802b26853e77" 52 | 53 | [[package]] 54 | name = "autocfg" 55 | version = "1.1.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 58 | 59 | [[package]] 60 | name = "bindgen" 61 | version = "0.64.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" 64 | dependencies = [ 65 | "bitflags 1.3.2", 66 | "cexpr", 67 | "clang-sys", 68 | "lazy_static", 69 | "lazycell", 70 | "peeking_take_while", 71 | "proc-macro2", 72 | "quote", 73 | "regex", 74 | "rustc-hash", 75 | "shlex", 76 | "syn", 77 | ] 78 | 79 | [[package]] 80 | name = "bitflags" 81 | version = "1.3.2" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 84 | 85 | [[package]] 86 | name = "bitflags" 87 | version = "2.0.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" 90 | 91 | [[package]] 92 | name = "bytemuck" 93 | version = "1.13.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" 96 | dependencies = [ 97 | "bytemuck_derive", 98 | ] 99 | 100 | [[package]] 101 | name = "bytemuck_derive" 102 | version = "1.4.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322" 105 | dependencies = [ 106 | "proc-macro2", 107 | "quote", 108 | "syn", 109 | ] 110 | 111 | [[package]] 112 | name = "cc" 113 | version = "1.0.79" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 116 | 117 | [[package]] 118 | name = "cexpr" 119 | version = "0.6.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 122 | dependencies = [ 123 | "nom", 124 | ] 125 | 126 | [[package]] 127 | name = "cfg-expr" 128 | version = "0.11.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" 131 | dependencies = [ 132 | "smallvec", 133 | ] 134 | 135 | [[package]] 136 | name = "cfg-if" 137 | version = "1.0.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 140 | 141 | [[package]] 142 | name = "clang-sys" 143 | version = "1.6.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a" 146 | dependencies = [ 147 | "glob", 148 | "libc", 149 | ] 150 | 151 | [[package]] 152 | name = "clap" 153 | version = "4.1.11" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "42dfd32784433290c51d92c438bb72ea5063797fc3cc9a21a8c4346bebbb2098" 156 | dependencies = [ 157 | "bitflags 2.0.2", 158 | "clap_derive", 159 | "clap_lex", 160 | "is-terminal", 161 | "once_cell", 162 | "strsim", 163 | "termcolor", 164 | ] 165 | 166 | [[package]] 167 | name = "clap_derive" 168 | version = "4.1.9" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" 171 | dependencies = [ 172 | "heck", 173 | "proc-macro-error", 174 | "proc-macro2", 175 | "quote", 176 | "syn", 177 | ] 178 | 179 | [[package]] 180 | name = "clap_lex" 181 | version = "0.3.3" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" 184 | dependencies = [ 185 | "os_str_bytes", 186 | ] 187 | 188 | [[package]] 189 | name = "cookie-factory" 190 | version = "0.3.2" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" 193 | 194 | [[package]] 195 | name = "core-foundation" 196 | version = "0.9.3" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 199 | dependencies = [ 200 | "core-foundation-sys", 201 | "libc", 202 | ] 203 | 204 | [[package]] 205 | name = "core-foundation-sys" 206 | version = "0.8.3" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 209 | 210 | [[package]] 211 | name = "core-graphics-types" 212 | version = "0.1.1" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" 215 | dependencies = [ 216 | "bitflags 1.3.2", 217 | "core-foundation", 218 | "foreign-types", 219 | "libc", 220 | ] 221 | 222 | [[package]] 223 | name = "crossbeam-queue" 224 | version = "0.3.8" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" 227 | dependencies = [ 228 | "cfg-if", 229 | "crossbeam-utils", 230 | ] 231 | 232 | [[package]] 233 | name = "crossbeam-utils" 234 | version = "0.8.15" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" 237 | dependencies = [ 238 | "cfg-if", 239 | ] 240 | 241 | [[package]] 242 | name = "crunchy" 243 | version = "0.2.2" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 246 | 247 | [[package]] 248 | name = "dlib" 249 | version = "0.5.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" 252 | dependencies = [ 253 | "libloading", 254 | ] 255 | 256 | [[package]] 257 | name = "downcast-rs" 258 | version = "1.2.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" 261 | 262 | [[package]] 263 | name = "enterpolation" 264 | version = "0.2.0" 265 | source = "git+https://github.com/NicolasKlenert/enterpolation/?rev=69ec96fbb150f6b389efab11826e1a9784fb907e#69ec96fbb150f6b389efab11826e1a9784fb907e" 266 | dependencies = [ 267 | "assert_float_eq", 268 | "num-traits", 269 | "topology-traits", 270 | ] 271 | 272 | [[package]] 273 | name = "errno" 274 | version = "0.2.8" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 277 | dependencies = [ 278 | "errno-dragonfly", 279 | "libc", 280 | "winapi", 281 | ] 282 | 283 | [[package]] 284 | name = "errno" 285 | version = "0.3.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" 288 | dependencies = [ 289 | "errno-dragonfly", 290 | "libc", 291 | "windows-sys 0.45.0", 292 | ] 293 | 294 | [[package]] 295 | name = "errno-dragonfly" 296 | version = "0.1.2" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 299 | dependencies = [ 300 | "cc", 301 | "libc", 302 | ] 303 | 304 | [[package]] 305 | name = "fastrand" 306 | version = "1.9.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 309 | dependencies = [ 310 | "instant", 311 | ] 312 | 313 | [[package]] 314 | name = "foreign-types" 315 | version = "0.3.2" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 318 | dependencies = [ 319 | "foreign-types-shared", 320 | ] 321 | 322 | [[package]] 323 | name = "foreign-types-shared" 324 | version = "0.1.1" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 327 | 328 | [[package]] 329 | name = "futures" 330 | version = "0.3.26" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" 333 | dependencies = [ 334 | "futures-channel", 335 | "futures-core", 336 | "futures-executor", 337 | "futures-io", 338 | "futures-sink", 339 | "futures-task", 340 | "futures-util", 341 | ] 342 | 343 | [[package]] 344 | name = "futures-channel" 345 | version = "0.3.26" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" 348 | dependencies = [ 349 | "futures-core", 350 | "futures-sink", 351 | ] 352 | 353 | [[package]] 354 | name = "futures-core" 355 | version = "0.3.26" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" 358 | 359 | [[package]] 360 | name = "futures-executor" 361 | version = "0.3.26" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" 364 | dependencies = [ 365 | "futures-core", 366 | "futures-task", 367 | "futures-util", 368 | ] 369 | 370 | [[package]] 371 | name = "futures-io" 372 | version = "0.3.26" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" 375 | 376 | [[package]] 377 | name = "futures-macro" 378 | version = "0.3.26" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" 381 | dependencies = [ 382 | "proc-macro2", 383 | "quote", 384 | "syn", 385 | ] 386 | 387 | [[package]] 388 | name = "futures-sink" 389 | version = "0.3.26" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" 392 | 393 | [[package]] 394 | name = "futures-task" 395 | version = "0.3.26" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" 398 | 399 | [[package]] 400 | name = "futures-util" 401 | version = "0.3.26" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" 404 | dependencies = [ 405 | "futures-channel", 406 | "futures-core", 407 | "futures-io", 408 | "futures-macro", 409 | "futures-sink", 410 | "futures-task", 411 | "memchr", 412 | "pin-project-lite", 413 | "pin-utils", 414 | "slab", 415 | ] 416 | 417 | [[package]] 418 | name = "getrandom" 419 | version = "0.2.8" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 422 | dependencies = [ 423 | "cfg-if", 424 | "libc", 425 | "wasi", 426 | ] 427 | 428 | [[package]] 429 | name = "glob" 430 | version = "0.3.1" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 433 | 434 | [[package]] 435 | name = "half" 436 | version = "2.2.1" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" 439 | dependencies = [ 440 | "crunchy", 441 | ] 442 | 443 | [[package]] 444 | name = "hashbrown" 445 | version = "0.12.3" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 448 | 449 | [[package]] 450 | name = "heck" 451 | version = "0.4.1" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 454 | 455 | [[package]] 456 | name = "hermit-abi" 457 | version = "0.3.1" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 460 | 461 | [[package]] 462 | name = "indexmap" 463 | version = "1.9.2" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 466 | dependencies = [ 467 | "autocfg", 468 | "hashbrown", 469 | ] 470 | 471 | [[package]] 472 | name = "instant" 473 | version = "0.1.12" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 476 | dependencies = [ 477 | "cfg-if", 478 | ] 479 | 480 | [[package]] 481 | name = "io-lifetimes" 482 | version = "1.0.6" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" 485 | dependencies = [ 486 | "libc", 487 | "windows-sys 0.45.0", 488 | ] 489 | 490 | [[package]] 491 | name = "is-terminal" 492 | version = "0.4.5" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" 495 | dependencies = [ 496 | "hermit-abi", 497 | "io-lifetimes", 498 | "rustix", 499 | "windows-sys 0.45.0", 500 | ] 501 | 502 | [[package]] 503 | name = "itoa" 504 | version = "1.0.6" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 507 | 508 | [[package]] 509 | name = "lazy_static" 510 | version = "1.4.0" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 513 | 514 | [[package]] 515 | name = "lazycell" 516 | version = "1.3.0" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 519 | 520 | [[package]] 521 | name = "libc" 522 | version = "0.2.139" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 525 | 526 | [[package]] 527 | name = "libloading" 528 | version = "0.7.4" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" 531 | dependencies = [ 532 | "cfg-if", 533 | "winapi", 534 | ] 535 | 536 | [[package]] 537 | name = "libspa" 538 | version = "0.6.0" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "667dfbb50c3d1f7ee1d33afdc04d1255923ece7642db3303046e7d63d997d77d" 541 | dependencies = [ 542 | "bitflags 1.3.2", 543 | "cc", 544 | "cookie-factory", 545 | "errno 0.3.0", 546 | "libc", 547 | "libspa-sys", 548 | "nom", 549 | "system-deps", 550 | ] 551 | 552 | [[package]] 553 | name = "libspa-sys" 554 | version = "0.6.0" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "79cf5b88f52534df7ca88d451ae9628e22124e3cc5c60966465a7db479534c7a" 557 | dependencies = [ 558 | "bindgen", 559 | "cc", 560 | "system-deps", 561 | ] 562 | 563 | [[package]] 564 | name = "linux-raw-sys" 565 | version = "0.1.4" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 568 | 569 | [[package]] 570 | name = "lock_api" 571 | version = "0.4.9" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 574 | dependencies = [ 575 | "autocfg", 576 | "scopeguard", 577 | ] 578 | 579 | [[package]] 580 | name = "log" 581 | version = "0.4.17" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 584 | dependencies = [ 585 | "cfg-if", 586 | ] 587 | 588 | [[package]] 589 | name = "malloc_buf" 590 | version = "0.0.6" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 593 | dependencies = [ 594 | "libc", 595 | ] 596 | 597 | [[package]] 598 | name = "memchr" 599 | version = "2.5.0" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 602 | 603 | [[package]] 604 | name = "memoffset" 605 | version = "0.7.1" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 608 | dependencies = [ 609 | "autocfg", 610 | ] 611 | 612 | [[package]] 613 | name = "minimal-lexical" 614 | version = "0.2.1" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 617 | 618 | [[package]] 619 | name = "nix" 620 | version = "0.26.2" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" 623 | dependencies = [ 624 | "bitflags 1.3.2", 625 | "cfg-if", 626 | "libc", 627 | "memoffset", 628 | "pin-utils", 629 | "static_assertions", 630 | ] 631 | 632 | [[package]] 633 | name = "nom" 634 | version = "7.1.3" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 637 | dependencies = [ 638 | "memchr", 639 | "minimal-lexical", 640 | ] 641 | 642 | [[package]] 643 | name = "num-complex" 644 | version = "0.4.3" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" 647 | dependencies = [ 648 | "num-traits", 649 | ] 650 | 651 | [[package]] 652 | name = "num-integer" 653 | version = "0.1.45" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 656 | dependencies = [ 657 | "autocfg", 658 | "num-traits", 659 | ] 660 | 661 | [[package]] 662 | name = "num-traits" 663 | version = "0.2.15" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 666 | dependencies = [ 667 | "autocfg", 668 | ] 669 | 670 | [[package]] 671 | name = "objc" 672 | version = "0.2.7" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 675 | dependencies = [ 676 | "malloc_buf", 677 | ] 678 | 679 | [[package]] 680 | name = "once_cell" 681 | version = "1.17.1" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 684 | 685 | [[package]] 686 | name = "os_str_bytes" 687 | version = "6.5.0" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" 690 | 691 | [[package]] 692 | name = "parking_lot" 693 | version = "0.12.1" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 696 | dependencies = [ 697 | "lock_api", 698 | "parking_lot_core", 699 | ] 700 | 701 | [[package]] 702 | name = "parking_lot_core" 703 | version = "0.9.7" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 706 | dependencies = [ 707 | "cfg-if", 708 | "libc", 709 | "redox_syscall", 710 | "smallvec", 711 | "windows-sys 0.45.0", 712 | ] 713 | 714 | [[package]] 715 | name = "peeking_take_while" 716 | version = "0.1.2" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 719 | 720 | [[package]] 721 | name = "pin-project-lite" 722 | version = "0.2.9" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 725 | 726 | [[package]] 727 | name = "pin-utils" 728 | version = "0.1.0" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 731 | 732 | [[package]] 733 | name = "pipewire" 734 | version = "0.6.0" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "dc2180a4a84b855be86e6cd72fa6fd4318278871d2b1082e7cd05fe64b135ccb" 737 | dependencies = [ 738 | "anyhow", 739 | "bitflags 1.3.2", 740 | "errno 0.3.0", 741 | "libc", 742 | "libspa", 743 | "libspa-sys", 744 | "nix", 745 | "once_cell", 746 | "pipewire-sys", 747 | "thiserror", 748 | ] 749 | 750 | [[package]] 751 | name = "pipewire-sys" 752 | version = "0.6.0" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "a95290eedb7fb6aa3922fdc0261cd0ddeb940abcdbdef28778928106554d2123" 755 | dependencies = [ 756 | "bindgen", 757 | "libspa-sys", 758 | "system-deps", 759 | ] 760 | 761 | [[package]] 762 | name = "pkg-config" 763 | version = "0.3.26" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 766 | 767 | [[package]] 768 | name = "primal-check" 769 | version = "0.3.3" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "9df7f93fd637f083201473dab4fee2db4c429d32e55e3299980ab3957ab916a0" 772 | dependencies = [ 773 | "num-integer", 774 | ] 775 | 776 | [[package]] 777 | name = "proc-macro-error" 778 | version = "1.0.4" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 781 | dependencies = [ 782 | "proc-macro-error-attr", 783 | "proc-macro2", 784 | "quote", 785 | "syn", 786 | "version_check", 787 | ] 788 | 789 | [[package]] 790 | name = "proc-macro-error-attr" 791 | version = "1.0.4" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 794 | dependencies = [ 795 | "proc-macro2", 796 | "quote", 797 | "version_check", 798 | ] 799 | 800 | [[package]] 801 | name = "proc-macro2" 802 | version = "1.0.51" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" 805 | dependencies = [ 806 | "unicode-ident", 807 | ] 808 | 809 | [[package]] 810 | name = "quick-xml" 811 | version = "0.23.1" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" 814 | dependencies = [ 815 | "memchr", 816 | ] 817 | 818 | [[package]] 819 | name = "quote" 820 | version = "1.0.23" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 823 | dependencies = [ 824 | "proc-macro2", 825 | ] 826 | 827 | [[package]] 828 | name = "redox_syscall" 829 | version = "0.2.16" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 832 | dependencies = [ 833 | "bitflags 1.3.2", 834 | ] 835 | 836 | [[package]] 837 | name = "regex" 838 | version = "1.7.1" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 841 | dependencies = [ 842 | "aho-corasick", 843 | "memchr", 844 | "regex-syntax", 845 | ] 846 | 847 | [[package]] 848 | name = "regex-syntax" 849 | version = "0.6.28" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 852 | 853 | [[package]] 854 | name = "rustc-hash" 855 | version = "1.1.0" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 858 | 859 | [[package]] 860 | name = "rustfft" 861 | version = "6.1.0" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "e17d4f6cbdb180c9f4b2a26bbf01c4e647f1e1dea22fe8eb9db54198b32f9434" 864 | dependencies = [ 865 | "num-complex", 866 | "num-integer", 867 | "num-traits", 868 | "primal-check", 869 | "strength_reduce", 870 | "transpose", 871 | "version_check", 872 | ] 873 | 874 | [[package]] 875 | name = "rustix" 876 | version = "0.36.9" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" 879 | dependencies = [ 880 | "bitflags 1.3.2", 881 | "errno 0.2.8", 882 | "io-lifetimes", 883 | "libc", 884 | "linux-raw-sys", 885 | "windows-sys 0.45.0", 886 | ] 887 | 888 | [[package]] 889 | name = "ryu" 890 | version = "1.0.13" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 893 | 894 | [[package]] 895 | name = "scoped-tls" 896 | version = "1.0.1" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 899 | 900 | [[package]] 901 | name = "scopeguard" 902 | version = "1.1.0" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 905 | 906 | [[package]] 907 | name = "serde" 908 | version = "1.0.153" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "3a382c72b4ba118526e187430bb4963cd6d55051ebf13d9b25574d379cc98d20" 911 | dependencies = [ 912 | "serde_derive", 913 | ] 914 | 915 | [[package]] 916 | name = "serde_derive" 917 | version = "1.0.153" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f" 920 | dependencies = [ 921 | "proc-macro2", 922 | "quote", 923 | "syn", 924 | ] 925 | 926 | [[package]] 927 | name = "serde_json" 928 | version = "1.0.94" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" 931 | dependencies = [ 932 | "itoa", 933 | "ryu", 934 | "serde", 935 | ] 936 | 937 | [[package]] 938 | name = "shlex" 939 | version = "1.1.0" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" 942 | 943 | [[package]] 944 | name = "slab" 945 | version = "0.4.8" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 948 | dependencies = [ 949 | "autocfg", 950 | ] 951 | 952 | [[package]] 953 | name = "smallvec" 954 | version = "1.10.0" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 957 | 958 | [[package]] 959 | name = "soa_derive" 960 | version = "0.12.0" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "327b898354ca1a9e0386aa10b19f684d181ae273c0e8ce9ad22464e9fbe39322" 963 | dependencies = [ 964 | "soa_derive_internal", 965 | ] 966 | 967 | [[package]] 968 | name = "soa_derive_internal" 969 | version = "0.12.0" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "7bcbaa3dd35156733d6754fa463a6852a51652deb71490e4c2b67b8c8f5535a9" 972 | dependencies = [ 973 | "proc-macro2", 974 | "quote", 975 | "syn", 976 | ] 977 | 978 | [[package]] 979 | name = "static_assertions" 980 | version = "1.1.0" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 983 | 984 | [[package]] 985 | name = "strength_reduce" 986 | version = "0.2.4" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" 989 | 990 | [[package]] 991 | name = "strsim" 992 | version = "0.10.0" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 995 | 996 | [[package]] 997 | name = "syn" 998 | version = "1.0.109" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1001 | dependencies = [ 1002 | "proc-macro2", 1003 | "quote", 1004 | "unicode-ident", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "system-deps" 1009 | version = "6.0.3" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" 1012 | dependencies = [ 1013 | "cfg-expr", 1014 | "heck", 1015 | "pkg-config", 1016 | "toml", 1017 | "version-compare", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "tempfile" 1022 | version = "3.4.0" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" 1025 | dependencies = [ 1026 | "cfg-if", 1027 | "fastrand", 1028 | "redox_syscall", 1029 | "rustix", 1030 | "windows-sys 0.42.0", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "termcolor" 1035 | version = "1.2.0" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 1038 | dependencies = [ 1039 | "winapi-util", 1040 | ] 1041 | 1042 | [[package]] 1043 | name = "thiserror" 1044 | version = "1.0.39" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" 1047 | dependencies = [ 1048 | "thiserror-impl", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "thiserror-impl" 1053 | version = "1.0.39" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" 1056 | dependencies = [ 1057 | "proc-macro2", 1058 | "quote", 1059 | "syn", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "thread_local" 1064 | version = "1.1.7" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 1067 | dependencies = [ 1068 | "cfg-if", 1069 | "once_cell", 1070 | ] 1071 | 1072 | [[package]] 1073 | name = "toml" 1074 | version = "0.5.11" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 1077 | dependencies = [ 1078 | "serde", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "topology-traits" 1083 | version = "0.1.1" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "6f34076ac0a15d54be52883b0987cb1b35dddede52ce4003c7a3a3490a9252ec" 1086 | dependencies = [ 1087 | "num-traits", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "transpose" 1092 | version = "0.2.2" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "e6522d49d03727ffb138ae4cbc1283d3774f0d10aa7f9bf52e6784c45daf9b23" 1095 | dependencies = [ 1096 | "num-integer", 1097 | "strength_reduce", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "unicode-ident" 1102 | version = "1.0.8" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 1105 | 1106 | [[package]] 1107 | name = "version-compare" 1108 | version = "0.1.1" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" 1111 | 1112 | [[package]] 1113 | name = "version_check" 1114 | version = "0.9.4" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1117 | 1118 | [[package]] 1119 | name = "visualiser" 1120 | version = "0.1.0" 1121 | dependencies = [ 1122 | "ahash", 1123 | "apodize", 1124 | "clap", 1125 | "enterpolation", 1126 | "futures", 1127 | "lazy_static", 1128 | "libspa-sys", 1129 | "pipewire", 1130 | "rustfft", 1131 | "soa_derive", 1132 | "tempfile", 1133 | "vulkano", 1134 | "wayland-backend", 1135 | "wayland-client", 1136 | "wayland-protocols", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "vk-parse" 1141 | version = "0.8.0" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "4c6a0bda9bbe6b9e50e6456c80aa8fe4cca3b21e4311a1130c41e4915ec2e32a" 1144 | dependencies = [ 1145 | "xml-rs", 1146 | ] 1147 | 1148 | [[package]] 1149 | name = "vulkano" 1150 | version = "0.32.3" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "f80b11c6c46ecb2c42155c8bcbc3ff04b783185181d4bbe19d9635a711ec747f" 1153 | dependencies = [ 1154 | "ahash", 1155 | "ash", 1156 | "bytemuck", 1157 | "core-graphics-types", 1158 | "crossbeam-queue", 1159 | "half", 1160 | "heck", 1161 | "indexmap", 1162 | "lazy_static", 1163 | "libloading", 1164 | "objc", 1165 | "parking_lot", 1166 | "proc-macro2", 1167 | "quote", 1168 | "regex", 1169 | "serde", 1170 | "serde_json", 1171 | "smallvec", 1172 | "thread_local", 1173 | "vk-parse", 1174 | ] 1175 | 1176 | [[package]] 1177 | name = "wasi" 1178 | version = "0.11.0+wasi-snapshot-preview1" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1181 | 1182 | [[package]] 1183 | name = "wayland-backend" 1184 | version = "0.1.1" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "79ebd48bfc1178c9190c7ff80cc822b3335ffc83141e9aa723168f377257623e" 1187 | dependencies = [ 1188 | "cc", 1189 | "downcast-rs", 1190 | "io-lifetimes", 1191 | "nix", 1192 | "scoped-tls", 1193 | "smallvec", 1194 | "wayland-sys", 1195 | ] 1196 | 1197 | [[package]] 1198 | name = "wayland-client" 1199 | version = "0.30.1" 1200 | source = "registry+https://github.com/rust-lang/crates.io-index" 1201 | checksum = "85bde68449abab1a808e5227b6e295f4ae3680911eb7711b4a2cb90141edb780" 1202 | dependencies = [ 1203 | "bitflags 1.3.2", 1204 | "nix", 1205 | "wayland-backend", 1206 | "wayland-scanner", 1207 | ] 1208 | 1209 | [[package]] 1210 | name = "wayland-protocols" 1211 | version = "0.30.0" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "7fefbeb8a360abe67ab7c2efe1d297a1a50ee011f5460791bc18870c26bb84e2" 1214 | dependencies = [ 1215 | "bitflags 1.3.2", 1216 | "wayland-backend", 1217 | "wayland-client", 1218 | "wayland-scanner", 1219 | ] 1220 | 1221 | [[package]] 1222 | name = "wayland-scanner" 1223 | version = "0.30.0" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "4834c14b3edf1d9986c83ca79b1e7e3afbe9874c7c144702f6467063259ce45d" 1226 | dependencies = [ 1227 | "proc-macro2", 1228 | "quick-xml", 1229 | "quote", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "wayland-sys" 1234 | version = "0.30.1" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" 1237 | dependencies = [ 1238 | "dlib", 1239 | "log", 1240 | "pkg-config", 1241 | ] 1242 | 1243 | [[package]] 1244 | name = "winapi" 1245 | version = "0.3.9" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1248 | dependencies = [ 1249 | "winapi-i686-pc-windows-gnu", 1250 | "winapi-x86_64-pc-windows-gnu", 1251 | ] 1252 | 1253 | [[package]] 1254 | name = "winapi-i686-pc-windows-gnu" 1255 | version = "0.4.0" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1258 | 1259 | [[package]] 1260 | name = "winapi-util" 1261 | version = "0.1.5" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1264 | dependencies = [ 1265 | "winapi", 1266 | ] 1267 | 1268 | [[package]] 1269 | name = "winapi-x86_64-pc-windows-gnu" 1270 | version = "0.4.0" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1273 | 1274 | [[package]] 1275 | name = "windows-sys" 1276 | version = "0.42.0" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 1279 | dependencies = [ 1280 | "windows_aarch64_gnullvm", 1281 | "windows_aarch64_msvc", 1282 | "windows_i686_gnu", 1283 | "windows_i686_msvc", 1284 | "windows_x86_64_gnu", 1285 | "windows_x86_64_gnullvm", 1286 | "windows_x86_64_msvc", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "windows-sys" 1291 | version = "0.45.0" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1294 | dependencies = [ 1295 | "windows-targets", 1296 | ] 1297 | 1298 | [[package]] 1299 | name = "windows-targets" 1300 | version = "0.42.1" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" 1303 | dependencies = [ 1304 | "windows_aarch64_gnullvm", 1305 | "windows_aarch64_msvc", 1306 | "windows_i686_gnu", 1307 | "windows_i686_msvc", 1308 | "windows_x86_64_gnu", 1309 | "windows_x86_64_gnullvm", 1310 | "windows_x86_64_msvc", 1311 | ] 1312 | 1313 | [[package]] 1314 | name = "windows_aarch64_gnullvm" 1315 | version = "0.42.1" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 1318 | 1319 | [[package]] 1320 | name = "windows_aarch64_msvc" 1321 | version = "0.42.1" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 1324 | 1325 | [[package]] 1326 | name = "windows_i686_gnu" 1327 | version = "0.42.1" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 1330 | 1331 | [[package]] 1332 | name = "windows_i686_msvc" 1333 | version = "0.42.1" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 1336 | 1337 | [[package]] 1338 | name = "windows_x86_64_gnu" 1339 | version = "0.42.1" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 1342 | 1343 | [[package]] 1344 | name = "windows_x86_64_gnullvm" 1345 | version = "0.42.1" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 1348 | 1349 | [[package]] 1350 | name = "windows_x86_64_msvc" 1351 | version = "0.42.1" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 1354 | 1355 | [[package]] 1356 | name = "xml-rs" 1357 | version = "0.8.4" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" 1360 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "visualiser" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | wayland-client = "0.30.0" 8 | vulkano = "0.32.0" 9 | pipewire = "0.6.0" 10 | tempfile = "3.3.0" 11 | wayland-protocols = { version = "0.30.0", features = ["client"] } 12 | wayland-backend = { version = "0.1.0", features = ["client_system"] } 13 | ahash = "0.8.3" 14 | futures = "0.3.26" 15 | libspa-sys = "0.6.0" 16 | rustfft = "6.1.0" 17 | enterpolation = { git = "https://github.com/NicolasKlenert/enterpolation/", rev = "69ec96fbb150f6b389efab11826e1a9784fb907e" } 18 | soa_derive = "0.12.0" 19 | apodize = "1.0.0" 20 | clap = { version = "4.1.0", features = ["derive"] } 21 | lazy_static = "1.4.0" 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ### GNU GENERAL PUBLIC LICENSE 2 | 3 | Version 2, June 1991 4 | 5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 6 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies 9 | of this license document, but changing it is not allowed. 10 | 11 | ### Preamble 12 | 13 | The licenses for most software are designed to take away your freedom 14 | to share and change it. By contrast, the GNU General Public License is 15 | intended to guarantee your freedom to share and change free 16 | software--to make sure the software is free for all its users. This 17 | General Public License applies to most of the Free Software 18 | Foundation's software and to any other program whose authors commit to 19 | using it. (Some other Free Software Foundation software is covered by 20 | the GNU Lesser General Public License instead.) You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not 24 | price. Our General Public Licenses are designed to make sure that you 25 | have the freedom to distribute copies of free software (and charge for 26 | this service if you wish), that you receive source code or can get it 27 | if you want it, that you can change the software or use pieces of it 28 | in new free programs; and that you know you can do these things. 29 | 30 | To protect your rights, we need to make restrictions that forbid 31 | anyone to deny you these rights or to ask you to surrender the rights. 32 | These restrictions translate to certain responsibilities for you if 33 | you distribute copies of the software, or if you modify it. 34 | 35 | For example, if you distribute copies of such a program, whether 36 | gratis or for a fee, you must give the recipients all the rights that 37 | you have. You must make sure that they, too, receive or can get the 38 | source code. And you must show them these terms so they know their 39 | rights. 40 | 41 | We protect your rights with two steps: (1) copyright the software, and 42 | (2) offer you this license which gives you legal permission to copy, 43 | distribute and/or modify the software. 44 | 45 | Also, for each author's protection and ours, we want to make certain 46 | that everyone understands that there is no warranty for this free 47 | software. If the software is modified by someone else and passed on, 48 | we want its recipients to know that what they have is not the 49 | original, so that any problems introduced by others will not reflect 50 | on the original authors' reputations. 51 | 52 | Finally, any free program is threatened constantly by software 53 | patents. We wish to avoid the danger that redistributors of a free 54 | program will individually obtain patent licenses, in effect making the 55 | program proprietary. To prevent this, we have made it clear that any 56 | patent must be licensed for everyone's free use or not licensed at 57 | all. 58 | 59 | The precise terms and conditions for copying, distribution and 60 | modification follow. 61 | 62 | ### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 63 | 64 | **0.** This License applies to any program or other work which 65 | contains a notice placed by the copyright holder saying it may be 66 | distributed under the terms of this General Public License. The 67 | "Program", below, refers to any such program or work, and a "work 68 | based on the Program" means either the Program or any derivative work 69 | under copyright law: that is to say, a work containing the Program or 70 | a portion of it, either verbatim or with modifications and/or 71 | translated into another language. (Hereinafter, translation is 72 | included without limitation in the term "modification".) Each licensee 73 | is addressed as "you". 74 | 75 | Activities other than copying, distribution and modification are not 76 | covered by this License; they are outside its scope. The act of 77 | running the Program is not restricted, and the output from the Program 78 | is covered only if its contents constitute a work based on the Program 79 | (independent of having been made by running the Program). Whether that 80 | is true depends on what the Program does. 81 | 82 | **1.** You may copy and distribute verbatim copies of the Program's 83 | source code as you receive it, in any medium, provided that you 84 | conspicuously and appropriately publish on each copy an appropriate 85 | copyright notice and disclaimer of warranty; keep intact all the 86 | notices that refer to this License and to the absence of any warranty; 87 | and give any other recipients of the Program a copy of this License 88 | along with the Program. 89 | 90 | You may charge a fee for the physical act of transferring a copy, and 91 | you may at your option offer warranty protection in exchange for a 92 | fee. 93 | 94 | **2.** You may modify your copy or copies of the Program or any 95 | portion of it, thus forming a work based on the Program, and copy and 96 | distribute such modifications or work under the terms of Section 1 97 | above, provided that you also meet all of these conditions: 98 | 99 | 100 | **a)** You must cause the modified files to carry prominent notices 101 | stating that you changed the files and the date of any change. 102 | 103 | 104 | **b)** You must cause any work that you distribute or publish, that in 105 | whole or in part contains or is derived from the Program or any part 106 | thereof, to be licensed as a whole at no charge to all third parties 107 | under the terms of this License. 108 | 109 | 110 | **c)** If the modified program normally reads commands interactively 111 | when run, you must cause it, when started running for such interactive 112 | use in the most ordinary way, to print or display an announcement 113 | including an appropriate copyright notice and a notice that there is 114 | no warranty (or else, saying that you provide a warranty) and that 115 | users may redistribute the program under these conditions, and telling 116 | the user how to view a copy of this License. (Exception: if the 117 | Program itself is interactive but does not normally print such an 118 | announcement, your work based on the Program is not required to print 119 | an announcement.) 120 | 121 | These requirements apply to the modified work as a whole. If 122 | identifiable sections of that work are not derived from the Program, 123 | and can be reasonably considered independent and separate works in 124 | themselves, then this License, and its terms, do not apply to those 125 | sections when you distribute them as separate works. But when you 126 | distribute the same sections as part of a whole which is a work based 127 | on the Program, the distribution of the whole must be on the terms of 128 | this License, whose permissions for other licensees extend to the 129 | entire whole, and thus to each and every part regardless of who wrote 130 | it. 131 | 132 | Thus, it is not the intent of this section to claim rights or contest 133 | your rights to work written entirely by you; rather, the intent is to 134 | exercise the right to control the distribution of derivative or 135 | collective works based on the Program. 136 | 137 | In addition, mere aggregation of another work not based on the Program 138 | with the Program (or with a work based on the Program) on a volume of 139 | a storage or distribution medium does not bring the other work under 140 | the scope of this License. 141 | 142 | **3.** You may copy and distribute the Program (or a work based on it, 143 | under Section 2) in object code or executable form under the terms of 144 | Sections 1 and 2 above provided that you also do one of the following: 145 | 146 | 147 | **a)** Accompany it with the complete corresponding machine-readable 148 | source code, which must be distributed under the terms of Sections 1 149 | and 2 above on a medium customarily used for software interchange; or, 150 | 151 | 152 | **b)** Accompany it with a written offer, valid for at least three 153 | years, to give any third party, for a charge no more than your cost of 154 | physically performing source distribution, a complete machine-readable 155 | copy of the corresponding source code, to be distributed under the 156 | terms of Sections 1 and 2 above on a medium customarily used for 157 | software interchange; or, 158 | 159 | 160 | **c)** Accompany it with the information you received as to the offer 161 | to distribute corresponding source code. (This alternative is allowed 162 | only for noncommercial distribution and only if you received the 163 | program in object code or executable form with such an offer, in 164 | accord with Subsection b above.) 165 | 166 | The source code for a work means the preferred form of the work for 167 | making modifications to it. For an executable work, complete source 168 | code means all the source code for all modules it contains, plus any 169 | associated interface definition files, plus the scripts used to 170 | control compilation and installation of the executable. However, as a 171 | special exception, the source code distributed need not include 172 | anything that is normally distributed (in either source or binary 173 | form) with the major components (compiler, kernel, and so on) of the 174 | operating system on which the executable runs, unless that component 175 | itself accompanies the executable. 176 | 177 | If distribution of executable or object code is made by offering 178 | access to copy from a designated place, then offering equivalent 179 | access to copy the source code from the same place counts as 180 | distribution of the source code, even though third parties are not 181 | compelled to copy the source along with the object code. 182 | 183 | **4.** You may not copy, modify, sublicense, or distribute the Program 184 | except as expressly provided under this License. Any attempt otherwise 185 | to copy, modify, sublicense or distribute the Program is void, and 186 | will automatically terminate your rights under this License. However, 187 | parties who have received copies, or rights, from you under this 188 | License will not have their licenses terminated so long as such 189 | parties remain in full compliance. 190 | 191 | **5.** You are not required to accept this License, since you have not 192 | signed it. However, nothing else grants you permission to modify or 193 | distribute the Program or its derivative works. These actions are 194 | prohibited by law if you do not accept this License. Therefore, by 195 | modifying or distributing the Program (or any work based on the 196 | Program), you indicate your acceptance of this License to do so, and 197 | all its terms and conditions for copying, distributing or modifying 198 | the Program or works based on it. 199 | 200 | **6.** Each time you redistribute the Program (or any work based on 201 | the Program), the recipient automatically receives a license from the 202 | original licensor to copy, distribute or modify the Program subject to 203 | these terms and conditions. You may not impose any further 204 | restrictions on the recipients' exercise of the rights granted herein. 205 | You are not responsible for enforcing compliance by third parties to 206 | this License. 207 | 208 | **7.** If, as a consequence of a court judgment or allegation of 209 | patent infringement or for any other reason (not limited to patent 210 | issues), conditions are imposed on you (whether by court order, 211 | agreement or otherwise) that contradict the conditions of this 212 | License, they do not excuse you from the conditions of this License. 213 | If you cannot distribute so as to satisfy simultaneously your 214 | obligations under this License and any other pertinent obligations, 215 | then as a consequence you may not distribute the Program at all. For 216 | example, if a patent license would not permit royalty-free 217 | redistribution of the Program by all those who receive copies directly 218 | or indirectly through you, then the only way you could satisfy both it 219 | and this License would be to refrain entirely from distribution of the 220 | Program. 221 | 222 | If any portion of this section is held invalid or unenforceable under 223 | any particular circumstance, the balance of the section is intended to 224 | apply and the section as a whole is intended to apply in other 225 | circumstances. 226 | 227 | It is not the purpose of this section to induce you to infringe any 228 | patents or other property right claims or to contest validity of any 229 | such claims; this section has the sole purpose of protecting the 230 | integrity of the free software distribution system, which is 231 | implemented by public license practices. Many people have made 232 | generous contributions to the wide range of software distributed 233 | through that system in reliance on consistent application of that 234 | system; it is up to the author/donor to decide if he or she is willing 235 | to distribute software through any other system and a licensee cannot 236 | impose that choice. 237 | 238 | This section is intended to make thoroughly clear what is believed to 239 | be a consequence of the rest of this License. 240 | 241 | **8.** If the distribution and/or use of the Program is restricted in 242 | certain countries either by patents or by copyrighted interfaces, the 243 | original copyright holder who places the Program under this License 244 | may add an explicit geographical distribution limitation excluding 245 | those countries, so that distribution is permitted only in or among 246 | countries not thus excluded. In such case, this License incorporates 247 | the limitation as if written in the body of this License. 248 | 249 | **9.** The Free Software Foundation may publish revised and/or new 250 | versions of the General Public License from time to time. Such new 251 | versions will be similar in spirit to the present version, but may 252 | differ in detail to address new problems or concerns. 253 | 254 | Each version is given a distinguishing version number. If the Program 255 | specifies a version number of this License which applies to it and 256 | "any later version", you have the option of following the terms and 257 | conditions either of that version or of any later version published by 258 | the Free Software Foundation. If the Program does not specify a 259 | version number of this License, you may choose any version ever 260 | published by the Free Software Foundation. 261 | 262 | **10.** If you wish to incorporate parts of the Program into other 263 | free programs whose distribution conditions are different, write to 264 | the author to ask for permission. For software which is copyrighted by 265 | the Free Software Foundation, write to the Free Software Foundation; 266 | we sometimes make exceptions for this. Our decision will be guided by 267 | the two goals of preserving the free status of all derivatives of our 268 | free software and of promoting the sharing and reuse of software 269 | generally. 270 | 271 | **NO WARRANTY** 272 | 273 | **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO 274 | WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 275 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 276 | OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY 277 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 278 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 279 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 280 | PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME 281 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 282 | 283 | **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 284 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 285 | AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU 286 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 287 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 288 | PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 289 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 290 | FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF 291 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 292 | DAMAGES. 293 | 294 | ### END OF TERMS AND CONDITIONS 295 | 296 | ### How to Apply These Terms to Your New Programs 297 | 298 | If you develop a new program, and you want it to be of the greatest 299 | possible use to the public, the best way to achieve this is to make it 300 | free software which everyone can redistribute and change under these 301 | terms. 302 | 303 | To do so, attach the following notices to the program. It is safest to 304 | attach them to the start of each source file to most effectively 305 | convey the exclusion of warranty; and each file should have at least 306 | the "copyright" line and a pointer to where the full notice is found. 307 | 308 | one line to give the program's name and an idea of what it does. 309 | Copyright (C) yyyy name of author 310 | 311 | This program is free software; you can redistribute it and/or 312 | modify it under the terms of the GNU General Public License 313 | as published by the Free Software Foundation; either version 2 314 | of the License, or (at your option) any later version. 315 | 316 | This program is distributed in the hope that it will be useful, 317 | but WITHOUT ANY WARRANTY; without even the implied warranty of 318 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 319 | GNU General Public License for more details. 320 | 321 | You should have received a copy of the GNU General Public License 322 | along with this program; if not, write to the Free Software 323 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 324 | 325 | Also add information on how to contact you by electronic and paper 326 | mail. 327 | 328 | If the program is interactive, make it output a short notice like this 329 | when it starts in an interactive mode: 330 | 331 | Gnomovision version 69, Copyright (C) year name of author 332 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details 333 | type `show w'. This is free software, and you are welcome 334 | to redistribute it under certain conditions; type `show c' 335 | for details. 336 | 337 | The hypothetical commands \`show w' and \`show c' should show the 338 | appropriate parts of the General Public License. Of course, the 339 | commands you use may be called something other than \`show w' and 340 | \`show c'; they could even be mouse-clicks or menu items--whatever 341 | suits your program. 342 | 343 | You should also get your employer (if you work as a programmer) or 344 | your school, if any, to sign a "copyright disclaimer" for the program, 345 | if necessary. Here is a sample; alter the names: 346 | 347 | Yoyodyne, Inc., hereby disclaims all copyright 348 | interest in the program `Gnomovision' 349 | (which makes passes at compilers) written 350 | by James Hacker. 351 | 352 | signature of Ty Coon, 1 April 1989 353 | Ty Coon, President of Vice 354 | 355 | This General Public License does not permit incorporating your program 356 | into proprietary programs. If your program is a subroutine library, 357 | you may consider it more useful to permit linking proprietary 358 | applications with the library. If this is what you want to do, use the 359 | [GNU Lesser General Public 360 | License](https://www.gnu.org/licenses/lgpl.html) instead of this 361 | License. 362 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlankParenthesis/visualiser/c447459adc809f94f31ccc58189a5400c5ff8862/demo.gif -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![rainbow frequency visualiser](demo.gif) 2 | 3 | Visualiser 4 | ========== 5 | 6 | A low level audio visualiser program for Linux using Wayland, Vulkan, and Pipewire, written in Rust. 7 | Inspired by [X.A.V.A](https://github.com/nikp123/xava). 8 | 9 | Supports arbitrary shapes (bars, radial, etc) using 2D vertex formats with attributes. 10 | 11 | *Note: Currently only a full area rectangle is used, but custom inputs should be possibly in future* 12 | 13 | Features 14 | -------- 15 | 16 | - Highly efficient 17 | - Flexible 18 | - Tuneable 19 | - Simple and lean 20 | 21 | ### Planned 22 | 23 | - Simple user specified patterns, shapes, and colors 24 | - Execute as shell component -------------------------------------------------------------------------------- /src/audio.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock}; 2 | 3 | use pipewire::{stream::*, properties, spa::{Direction, pod::{deserialize::PodDeserializer, Value}, utils::Id}, MainLoop}; 4 | 5 | use crate::{audio::pod_choice_default::Fixate, visualiser::BufferManager}; 6 | 7 | mod spa_audio_info_raw; 8 | mod pod_choice_default; 9 | 10 | #[derive(Debug)] 11 | struct StreamConfiguration { 12 | rate: u32, 13 | channels: u32, 14 | format: u32, 15 | } 16 | 17 | #[derive(Default)] 18 | struct StreamData { 19 | configuration: Option, 20 | visualiser: Arc>, 21 | } 22 | 23 | pub(crate) fn main( 24 | visualiser: Arc>, 25 | ) { 26 | std::thread::spawn(move || { 27 | let mainloop = MainLoop::new().unwrap(); 28 | let _stream = stream(&mainloop, visualiser); 29 | mainloop.run(); 30 | }); 31 | } 32 | 33 | fn stream( 34 | mainloop: &MainLoop, 35 | visualiser: Arc>, 36 | ) -> Stream { 37 | let stream = Stream::::with_user_data( 38 | mainloop, 39 | "audio-capture", 40 | properties! { 41 | *pipewire::keys::NODE_NAME => env!("CARGO_PKG_NAME"), 42 | *pipewire::keys::MEDIA_TYPE => "Audio", 43 | *pipewire::keys::MEDIA_CATEGORY => "Capture", 44 | *pipewire::keys::STREAM_CAPTURE_SINK => "true", 45 | }, 46 | StreamData { 47 | configuration: None, 48 | visualiser, 49 | }, 50 | ) 51 | .param_changed(|id, data, raw_pod| { 52 | if id == libspa_sys::SPA_PARAM_Format { 53 | let pointer = std::ptr::NonNull::new(raw_pod.cast_mut()).unwrap(); 54 | let object = unsafe { 55 | PodDeserializer::deserialize_ptr::(pointer).unwrap() 56 | }; 57 | 58 | if let Value::Object(object) = object { 59 | data.configuration = None; 60 | 61 | let media_type: Id = object.properties.iter() 62 | .find(|p| p.key == libspa_sys::SPA_FORMAT_mediaType) 63 | .unwrap().value 64 | .fixate().unwrap(); 65 | 66 | let media_subtype: Id = object.properties.iter() 67 | .find(|p| p.key == libspa_sys::SPA_FORMAT_mediaSubtype) 68 | .unwrap().value 69 | .fixate().unwrap(); 70 | 71 | let format: Id = object.properties.iter() 72 | .find(|p| p.key == libspa_sys::SPA_FORMAT_AUDIO_format) 73 | .unwrap().value 74 | .fixate().unwrap(); 75 | 76 | let rate: i32 = object.properties.iter() 77 | .find(|p| p.key == libspa_sys::SPA_FORMAT_AUDIO_rate) 78 | .unwrap().value 79 | .fixate().unwrap(); 80 | 81 | let channels: i32 = object.properties.iter() 82 | .find(|p| p.key == libspa_sys::SPA_FORMAT_AUDIO_channels) 83 | .unwrap().value 84 | .fixate().unwrap(); 85 | 86 | let is_audio = media_type.0 == libspa_sys::SPA_MEDIA_TYPE_audio; 87 | let is_raw = media_subtype.0 == libspa_sys::SPA_MEDIA_SUBTYPE_raw; 88 | if is_audio && is_raw { 89 | data.configuration = Some(StreamConfiguration { 90 | rate: rate as u32, 91 | channels: channels as u32, 92 | format: format.0, 93 | }); 94 | } 95 | } 96 | } 97 | }) 98 | .process(|stream, StreamData { configuration, visualiser }| { 99 | if let Some(mut buffer) = stream.dequeue_buffer() { 100 | // TODO: this is just the left channel — maybe handle all channels 101 | let channel = buffer.datas_mut().get_mut(0).unwrap(); 102 | let offset = channel.chunk().offset(); 103 | let chunk = channel.chunk(); 104 | let size = chunk.size() as usize; 105 | let stride = chunk.stride(); 106 | let data = channel.data(); 107 | 108 | if let Some(data) = data { 109 | let rate = configuration.as_ref().unwrap().rate; 110 | 111 | let cast_buffer: &[f32] = unsafe { 112 | std::slice::from_raw_parts(data.as_ptr().cast(), size / std::mem::size_of::()) 113 | }; 114 | visualiser.write().unwrap() 115 | .fill_buffer(cast_buffer, rate) 116 | } 117 | } 118 | }) 119 | .create().unwrap(); 120 | 121 | let params = spa_audio_info_raw::SpaAudioInfoRaw::empty().as_pod().unwrap(); 122 | 123 | stream.connect( 124 | Direction::Input, 125 | None, 126 | StreamFlags::AUTOCONNECT | StreamFlags::RT_PROCESS | StreamFlags::MAP_BUFFERS, 127 | &mut [params.as_ptr().cast()], 128 | ).unwrap(); 129 | 130 | stream 131 | } 132 | -------------------------------------------------------------------------------- /src/audio/pod_choice_default.rs: -------------------------------------------------------------------------------- 1 | use libspa_sys::{spa_rectangle as Rectangle, spa_fraction as Fraction}; 2 | use pipewire::{spa::{pod::{ChoiceValue, CanonicalFixedSizedPod, Value}, utils::{Choice, ChoiceEnum, Id, Fd}}}; 3 | 4 | pub trait Fixate { 5 | fn fixate(&self) -> Result; 6 | } 7 | 8 | impl Fixate for Value { 9 | fn fixate(&self) -> Result { 10 | match self { 11 | Value::Int(int) => Ok(*int), 12 | Value::Choice(choice) => choice.choice_default(), 13 | _ => Err(()), 14 | } 15 | } 16 | } 17 | 18 | impl Fixate for Value { 19 | fn fixate(&self) -> Result { 20 | match self { 21 | Value::Id(id) => Ok(*id), 22 | Value::Choice(choice) => choice.choice_default(), 23 | _ => Err(()), 24 | } 25 | } 26 | } 27 | 28 | impl Fixate for Value { 29 | fn fixate(&self) -> Result { 30 | match self { 31 | Value::Float(id) => Ok(*id), 32 | Value::Choice(choice) => choice.choice_default(), 33 | _ => Err(()), 34 | } 35 | } 36 | } 37 | 38 | pub trait ChoiceDefault { 39 | fn choice_default(&self) -> Result; 40 | } 41 | 42 | impl ChoiceDefault for Choice { 43 | fn choice_default(&self) -> Result { 44 | Ok(match &self.1 { 45 | ChoiceEnum::None(value) => *value, 46 | ChoiceEnum::Range { default, .. } => *default, 47 | ChoiceEnum::Step { default, .. } => *default, 48 | ChoiceEnum::Enum { default, .. } => *default, 49 | ChoiceEnum::Flags { default, .. } => *default, 50 | }) 51 | } 52 | } 53 | 54 | impl ChoiceDefault for ChoiceValue { 55 | fn choice_default(&self) -> Result { 56 | if let ChoiceValue::Int(choice) = self { 57 | choice.choice_default() 58 | } else { 59 | Err(()) 60 | } 61 | } 62 | } 63 | 64 | impl ChoiceDefault for ChoiceValue { 65 | fn choice_default(&self) -> Result { 66 | if let ChoiceValue::Long(choice) = self { 67 | choice.choice_default() 68 | } else { 69 | Err(()) 70 | } 71 | } 72 | } 73 | 74 | impl ChoiceDefault for ChoiceValue { 75 | fn choice_default(&self) -> Result { 76 | if let ChoiceValue::Float(choice) = self { 77 | choice.choice_default() 78 | } else { 79 | Err(()) 80 | } 81 | } 82 | } 83 | 84 | impl ChoiceDefault for ChoiceValue { 85 | fn choice_default(&self) -> Result { 86 | if let ChoiceValue::Double(choice) = self { 87 | choice.choice_default() 88 | } else { 89 | Err(()) 90 | } 91 | } 92 | } 93 | 94 | impl ChoiceDefault for ChoiceValue { 95 | fn choice_default(&self) -> Result { 96 | if let ChoiceValue::Id(choice) = self { 97 | choice.choice_default() 98 | } else { 99 | Err(()) 100 | } 101 | } 102 | } 103 | 104 | impl ChoiceDefault for ChoiceValue { 105 | fn choice_default(&self) -> Result { 106 | if let ChoiceValue::Rectangle(choice) = self { 107 | choice.choice_default() 108 | } else { 109 | Err(()) 110 | } 111 | } 112 | } 113 | 114 | impl ChoiceDefault for ChoiceValue { 115 | fn choice_default(&self) -> Result { 116 | if let ChoiceValue::Fraction(choice) = self { 117 | choice.choice_default() 118 | } else { 119 | Err(()) 120 | } 121 | } 122 | } 123 | impl ChoiceDefault for ChoiceValue { 124 | fn choice_default(&self) -> Result { 125 | if let ChoiceValue::Fd(choice) = self { 126 | choice.choice_default() 127 | } else { 128 | Err(()) 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /src/audio/spa_audio_info_raw.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Write, Seek, Cursor}; 2 | 3 | use pipewire::{spa::{pod::{serialize::*, PropertyFlags}, utils::Id}}; 4 | 5 | type ChannelPosition = libspa_sys::spa_audio_channel; 6 | 7 | // TODO: enums for format and ChannelPosition 8 | 9 | pub(crate) struct SpaAudioInfoRaw { 10 | pub format: libspa_sys::spa_audio_format, 11 | pub flags: u32, 12 | pub rate: u32, 13 | pub channels: Vec>, 14 | } 15 | 16 | impl SpaAudioInfoRaw { 17 | pub fn empty() -> Self { 18 | Self { 19 | format: libspa_sys::SPA_AUDIO_FORMAT_UNKNOWN, 20 | flags: 0, 21 | rate: 0, 22 | channels: vec![] 23 | } 24 | } 25 | } 26 | 27 | impl SpaAudioInfoRaw { 28 | pub fn as_pod(&self) -> Result, GenError> { 29 | let mut pod = Vec::::new(); 30 | let cursor = Cursor::new(&mut pod); 31 | 32 | PodSerializer::serialize(cursor, self)?; 33 | 34 | Ok(pod.into_boxed_slice()) 35 | } 36 | } 37 | 38 | impl PodSerialize for SpaAudioInfoRaw { 39 | fn serialize( 40 | &self, 41 | serializer: PodSerializer, 42 | ) -> Result, GenError> { 43 | let mut object_serializer = serializer.serialize_object( 44 | libspa_sys::SPA_TYPE_OBJECT_Format, 45 | libspa_sys::SPA_PARAM_EnumFormat, 46 | )?; 47 | object_serializer.serialize_property( 48 | libspa_sys::SPA_FORMAT_mediaType, 49 | &Id(libspa_sys::SPA_MEDIA_TYPE_audio), 50 | PropertyFlags::READONLY, 51 | )?; 52 | object_serializer.serialize_property( 53 | libspa_sys::SPA_FORMAT_mediaSubtype, 54 | &Id(libspa_sys::SPA_MEDIA_SUBTYPE_raw), 55 | PropertyFlags::READONLY, 56 | )?; 57 | if self.format != libspa_sys::SPA_AUDIO_FORMAT_UNKNOWN { 58 | object_serializer.serialize_property( 59 | libspa_sys::SPA_FORMAT_AUDIO_format, 60 | &Id(self.format), 61 | PropertyFlags::READONLY, 62 | )?; 63 | } 64 | if self.rate != 0 { 65 | object_serializer.serialize_property( 66 | libspa_sys::SPA_FORMAT_AUDIO_rate, 67 | &Id(self.rate), 68 | PropertyFlags::READONLY, 69 | )?; 70 | } 71 | if !self.channels.is_empty() { 72 | object_serializer.serialize_property( 73 | libspa_sys::SPA_FORMAT_AUDIO_channels, 74 | &Id(self.channels.len() as u32), 75 | PropertyFlags::READONLY, 76 | )?; 77 | 78 | if self.flags & libspa_sys::SPA_AUDIO_FLAG_UNPOSITIONED == 0 { 79 | let channels = self.channels.iter() 80 | .map(|c| match c { 81 | Some(id) => Id(*id), 82 | None => Id(0), 83 | }) 84 | .collect::>(); 85 | 86 | object_serializer.serialize_property( 87 | libspa_sys::SPA_FORMAT_AUDIO_position, 88 | channels.as_slice(), 89 | PropertyFlags::READONLY, 90 | )?; 91 | } 92 | } 93 | object_serializer.end() 94 | } 95 | } -------------------------------------------------------------------------------- /src/graphics.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use wayland_client::protocol::wl_display::WlDisplay; 4 | use wayland_client::protocol::wl_surface::WlSurface; 5 | 6 | use vulkano::{instance::*, sync::{self, FlushError}}; 7 | use vulkano::shader::*; 8 | 9 | use vulkano::VulkanLibrary; 10 | 11 | use vulkano::sync::GpuFuture; 12 | 13 | use crate::BUFFER_SIZE; 14 | 15 | use self::{surface::Surface, swapchain::Swapchain, vertex::{VisualiserVertex,VisualiserVertexVec}, sampler::Sampler, device::Device}; 16 | 17 | mod swapchain; 18 | mod surface; 19 | mod pipeline; 20 | mod device; 21 | 22 | mod vertex; 23 | mod sampler; 24 | 25 | const INSTANCE_EXTENSIONS: InstanceExtensions = InstanceExtensions { 26 | khr_surface: true, 27 | khr_wayland_surface: true, 28 | .. InstanceExtensions::empty() 29 | }; 30 | 31 | pub(crate) struct Graphics { 32 | instance: Arc, 33 | device: Device, 34 | surface: Surface, 35 | swapchain: Swapchain, 36 | visualiser_sampler: Sampler, 37 | previous_frame_future: Option>, 38 | } 39 | 40 | struct ShaderSpecializations {} 41 | unsafe impl SpecializationConstants for ShaderSpecializations { 42 | fn descriptors() -> &'static [SpecializationMapEntry] { 43 | static DESCRIPTORS: [SpecializationMapEntry; 0] = []; 44 | 45 | &DESCRIPTORS 46 | } 47 | } 48 | 49 | impl Graphics { 50 | fn instance() -> Arc { 51 | let library = VulkanLibrary::new() 52 | .expect("Failed to load vulkan library"); 53 | 54 | Instance::new( 55 | library, 56 | InstanceCreateInfo { 57 | enabled_extensions: INSTANCE_EXTENSIONS, 58 | .. Default::default() 59 | } 60 | ).expect("Couldn't build instance") 61 | } 62 | 63 | pub fn new( 64 | display: &WlDisplay, 65 | surface: &WlSurface, 66 | extent: [u32; 2], 67 | ) -> Self { 68 | let instance = Self::instance(); 69 | 70 | let (surface, device) = Surface::from_wayland(Arc::clone(&instance), display, surface); 71 | 72 | let visualiser_sampler = Sampler::new(&device); 73 | 74 | let mut vertices = VisualiserVertexVec::with_capacity(6); 75 | 76 | vertices.push(VisualiserVertex { position: [-1.0, -1.0], frequency: 0.0, amplitude: 1.0 }); 77 | vertices.push(VisualiserVertex { position: [1.0, -1.0], frequency: 1.0, amplitude: 1.0 }); 78 | vertices.push(VisualiserVertex { position: [1.0, 1.0], frequency: 1.0, amplitude: 0.0 }); 79 | vertices.push(VisualiserVertex { position: [-1.0, -1.0], frequency: 0.0, amplitude: 1.0 }); 80 | vertices.push(VisualiserVertex { position: [-1.0, 1.0], frequency: 0.0, amplitude: 0.0 }); 81 | vertices.push(VisualiserVertex { position: [1.0, 1.0], frequency: 1.0, amplitude: 0.0 }); 82 | 83 | let swapchain = Swapchain::new(&device, &surface, &visualiser_sampler, &vertices, extent); 84 | 85 | let previous_frame_future = Some(sync::now((&device).into()).boxed()); 86 | 87 | Graphics { 88 | instance, 89 | device, 90 | surface, 91 | swapchain, 92 | previous_frame_future, 93 | visualiser_sampler, 94 | } 95 | } 96 | 97 | pub fn draw(&mut self, buffer: Option>) { 98 | let mut previous_future = self.previous_frame_future.take() 99 | .unwrap_or_else(|| sync::now((&self.device).into()).boxed()); 100 | 101 | previous_future.cleanup_finished(); 102 | 103 | // If data is none, we don't need to update the surface. 104 | // However, wayland will not send the next frame callback until we do. 105 | // So, we draw anyway. 106 | if let Some(data) = buffer { 107 | match self.visualiser_sampler.buffer.write() { 108 | Ok(mut visualiser) => visualiser.copy_from_slice(data.as_slice()), 109 | Err(_) => { 110 | self.previous_frame_future = Some(previous_future); 111 | // if we can't change the buffer then the frame would be the same 112 | return; 113 | } 114 | } 115 | } 116 | 117 | let (acquire_future, present_info, command_buffer) = self.swapchain.next(); 118 | 119 | let future = previous_future 120 | .join(acquire_future) 121 | .then_execute( 122 | Arc::clone(&self.device.queue), 123 | command_buffer, 124 | ).unwrap() 125 | .then_swapchain_present( 126 | Arc::clone(&self.device.queue), 127 | present_info, 128 | ) 129 | .then_signal_fence_and_flush(); 130 | 131 | match future { 132 | Ok(future) => { 133 | self.previous_frame_future = Some(future.boxed()); 134 | }, 135 | Err(FlushError::OutOfDate) => { 136 | todo!() 137 | }, 138 | Err(e) => { 139 | todo!("{:?}", e) 140 | } 141 | } 142 | } 143 | } 144 | 145 | -------------------------------------------------------------------------------- /src/graphics/device.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::{device::{Device as VkDevice, DeviceCreateInfo, QueueCreateInfo, Features, DeviceExtensions, physical::{PhysicalDevice, PhysicalDeviceType, PhysicalDeviceError}, Queue}, instance::Instance, swapchain::{Surface, SurfaceCapabilities}, memory::allocator::StandardMemoryAllocator, descriptor_set::allocator::StandardDescriptorSetAllocator, command_buffer::allocator::StandardCommandBufferAllocator}; 4 | 5 | pub(crate) struct Device { 6 | device: Arc, 7 | pub queue_family_index: u32, 8 | pub queue: Arc, 9 | 10 | pub memory_allocator: StandardMemoryAllocator, 11 | pub descriptor_allocator: StandardDescriptorSetAllocator, 12 | pub command_buffer_allocator: StandardCommandBufferAllocator, 13 | } 14 | 15 | impl From<&Device> for Arc { 16 | fn from(device: &Device) -> Self { 17 | Arc::clone(&device.device) 18 | } 19 | } 20 | 21 | const DEVICE_EXTENSIONS: DeviceExtensions = DeviceExtensions { 22 | khr_swapchain: true, 23 | .. DeviceExtensions::empty() 24 | }; 25 | 26 | impl Device { 27 | pub fn new(instance: &Arc, surface: &Arc) -> Self { 28 | let (physical_device, queue_family_index) = Self::choose_device(instance, surface) 29 | .expect("No suitable graphics device"); 30 | 31 | let (device, mut queues) = VkDevice::new( 32 | physical_device, 33 | DeviceCreateInfo { 34 | queue_create_infos: vec![QueueCreateInfo { 35 | queue_family_index, 36 | ..Default::default() 37 | }], 38 | enabled_extensions: DEVICE_EXTENSIONS, 39 | enabled_features: Features { 40 | dynamic_rendering: true, 41 | ..Features::empty() 42 | }, 43 | ..Default::default() 44 | } 45 | ).expect("Failed to create graphics device"); 46 | 47 | let queue = queues.next().unwrap(); 48 | 49 | let memory_allocator = StandardMemoryAllocator::new_default(Arc::clone(&device)); 50 | let descriptor_allocator = StandardDescriptorSetAllocator::new(Arc::clone(&device)); 51 | let command_buffer_allocator = StandardCommandBufferAllocator::new(Arc::clone(&device), Default::default()); 52 | 53 | Self { 54 | device, 55 | queue_family_index, 56 | queue, 57 | memory_allocator, 58 | descriptor_allocator, 59 | command_buffer_allocator, 60 | } 61 | } 62 | 63 | pub fn choose_device( 64 | instance: &Arc, 65 | surface: &Arc, 66 | ) -> Option<(Arc, u32)> { 67 | instance.enumerate_physical_devices() 68 | .unwrap() 69 | .filter(|device| device.supported_extensions().contains(&DEVICE_EXTENSIONS)) 70 | .filter_map(|device| { 71 | device.queue_family_properties() 72 | .iter() 73 | .enumerate() 74 | .position(|(i, queue_properties)| { 75 | let supports_graphics = queue_properties.queue_flags.graphics; 76 | let supports_surface = device.surface_support(i as u32, surface).unwrap_or(false); 77 | 78 | supports_graphics && supports_surface 79 | }) 80 | .map(|i| (device, i as u32)) 81 | }) 82 | .min_by_key(|(device, _)| { 83 | match device.properties().device_type { 84 | PhysicalDeviceType::DiscreteGpu => 0, 85 | PhysicalDeviceType::IntegratedGpu => 1, 86 | PhysicalDeviceType::VirtualGpu => 2, 87 | PhysicalDeviceType::Cpu => 3, 88 | PhysicalDeviceType::Other => 4, 89 | _ => 5, 90 | } 91 | }) 92 | } 93 | 94 | pub fn physical_device(&self) -> &PhysicalDevice { 95 | self.device.physical_device().as_ref() 96 | } 97 | } -------------------------------------------------------------------------------- /src/graphics/pipeline.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::{command_buffer::PrimaryAutoCommandBuffer}; 4 | use vulkano::command_buffer::allocator::StandardCommandBufferAlloc; 5 | use vulkano::format::Format; 6 | use vulkano::device::Device; 7 | use vulkano::shader::ShaderModule; 8 | use vulkano::pipeline::{GraphicsPipeline, Pipeline as VkPipeline, PipelineLayout}; 9 | use vulkano::shader::spirv::ExecutionModel; 10 | use vulkano::pipeline::graphics::viewport::ViewportState; 11 | use vulkano::pipeline::graphics::render_pass::PipelineRenderingCreateInfo; 12 | 13 | use super::vertex::Vertex; 14 | use super::{vertex::VisualiserVertex, ShaderSpecializations}; 15 | 16 | pub(crate) struct Pipeline { 17 | pipeline: Arc, 18 | format: Format, 19 | } 20 | 21 | impl From<&Pipeline> for Arc { 22 | fn from(pipeline: &Pipeline) -> Self { 23 | Arc::clone(&pipeline.pipeline) 24 | } 25 | } 26 | 27 | impl Pipeline { 28 | pub fn layout(&self) -> Arc { 29 | Arc::clone(&self.pipeline.layout()) 30 | } 31 | 32 | pub fn new(device: Arc, format: Format) -> Self { 33 | let vs = unsafe { 34 | ShaderModule::from_bytes( 35 | Arc::clone(&device), 36 | include_bytes!("../shaders/vert.spv") 37 | ).unwrap() 38 | }; 39 | 40 | let fs = unsafe { 41 | ShaderModule::from_bytes( 42 | Arc::clone(&device), 43 | include_bytes!("../shaders/frag.spv") 44 | ).unwrap() 45 | }; 46 | 47 | let vertex = vs.entry_point_with_execution("main", ExecutionModel::Vertex).unwrap(); 48 | let fragment = fs.entry_point_with_execution("main", ExecutionModel::Fragment).unwrap(); 49 | 50 | let pipeline = GraphicsPipeline::start() 51 | .vertex_shader(vertex, ShaderSpecializations {}) 52 | .fragment_shader(fragment, ShaderSpecializations {}) 53 | .render_pass(PipelineRenderingCreateInfo { 54 | color_attachment_formats: vec![Some(format)], 55 | ..Default::default() 56 | }) 57 | .vertex_input_state(VisualiserVertex::input_state()) 58 | .viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant()) 59 | .build(device).unwrap(); 60 | 61 | Self { pipeline, format } 62 | } 63 | } -------------------------------------------------------------------------------- /src/graphics/sampler.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::{image::{StorageImage, ImageDimensions, view::ImageView}, descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet}, sampler::{Sampler as VkSampler, SamplerCreateInfo, SamplerAddressMode, Filter}, format::Format, buffer::{BufferUsage, CpuAccessibleBuffer}, command_buffer::CopyBufferToImageInfo, pipeline::{GraphicsPipeline, Pipeline}}; 4 | 5 | use crate::BUFFER_SIZE; 6 | 7 | use super::device::Device; 8 | 9 | pub(crate) struct Sampler { 10 | sampler: Arc, 11 | pub buffer: Arc>, 12 | image_view: Arc>, 13 | } 14 | 15 | impl Sampler { 16 | pub fn new(device: &Device) -> Self { 17 | let buffer = CpuAccessibleBuffer::from_iter( 18 | &device.memory_allocator, 19 | BufferUsage { 20 | transfer_src: true, 21 | ..BufferUsage::empty() 22 | }, 23 | true, 24 | [f32::default(); BUFFER_SIZE].into_iter(), 25 | ).unwrap(); 26 | 27 | let image = StorageImage::new( 28 | &device.memory_allocator, 29 | ImageDimensions::Dim1d { 30 | width: BUFFER_SIZE as u32, 31 | array_layers: 1, 32 | }, 33 | Format::R32_SFLOAT, 34 | [device.queue_family_index], 35 | ).unwrap(); 36 | 37 | let image_view = ImageView::new_default(image).unwrap(); 38 | 39 | let sampler = VkSampler::new( 40 | device.into(), 41 | SamplerCreateInfo { 42 | mag_filter: Filter::Linear, 43 | min_filter: Filter::Linear, 44 | address_mode: [SamplerAddressMode::ClampToEdge; 3], 45 | lod: 0.0..=1.0, 46 | ..Default::default() 47 | } 48 | ).unwrap(); 49 | 50 | Self { sampler, buffer, image_view } 51 | } 52 | 53 | pub fn descriptor_set(&self, device: &Device, pipeline: Arc) -> Arc { 54 | PersistentDescriptorSet::new( 55 | &device.descriptor_allocator, 56 | Arc::clone(pipeline.layout().set_layouts().first().unwrap()), 57 | [WriteDescriptorSet::image_view_sampler(0, self.image_view.clone(), Arc::clone(&self.sampler))], 58 | ).unwrap() 59 | } 60 | 61 | pub fn copy_operation(&self) -> CopyBufferToImageInfo { 62 | CopyBufferToImageInfo::buffer_image( 63 | self.buffer.clone(), 64 | self.image_view.image().clone(), 65 | ) 66 | } 67 | } -------------------------------------------------------------------------------- /src/graphics/surface.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::instance::Instance; 4 | use wayland_client::Proxy; 5 | use wayland_client::protocol::wl_display::WlDisplay; 6 | use wayland_client::protocol::wl_surface::WlSurface; 7 | 8 | use vulkano::format::Format; 9 | use vulkano::swapchain::{Surface as VkSurface, *}; 10 | 11 | use super::device::Device; 12 | 13 | pub(crate) struct Surface { 14 | pub surface: Arc, 15 | pub format: Format, 16 | pub alpha_mode: CompositeAlpha, 17 | pub transform: SurfaceTransform, 18 | pub framebuffer_count: u32, 19 | } 20 | 21 | impl From<&Surface> for Arc { 22 | fn from(surface: &Surface) -> Self { 23 | Arc::clone(&surface.surface) 24 | } 25 | } 26 | 27 | impl Surface { 28 | pub fn from_wayland( 29 | instance: Arc, 30 | display: &WlDisplay, 31 | surface: &WlSurface, 32 | ) -> (Self, Device) { 33 | let display_pointer = display.id().as_ptr(); 34 | let surface_pointer = surface.id().as_ptr(); 35 | 36 | let surface = unsafe { 37 | VkSurface::from_wayland( 38 | Arc::clone(&instance), 39 | display_pointer, 40 | surface_pointer, 41 | None, 42 | ) 43 | }.expect("Failed to create vulkan surface"); 44 | 45 | let device = Device::new(&instance, &surface); 46 | 47 | let capabilities = device.physical_device() 48 | .surface_capabilities(&surface, Default::default()) 49 | .expect("Device failed to provide surface capabilities"); 50 | 51 | let formats = device.physical_device() 52 | .surface_formats(&surface, Default::default()) 53 | .unwrap(); 54 | 55 | let format = formats.into_iter() 56 | .map(|f| f.0) 57 | .find(|format| { 58 | matches!(format, Format::B8G8R8A8_SRGB 59 | | Format::B8G8R8A8_UNORM 60 | | Format::R8G8B8A8_SRGB 61 | | Format::R8G8B8A8_UNORM 62 | ) 63 | }) 64 | .expect("Failed to find suitable format for surface"); 65 | 66 | let alpha_mode = capabilities.supported_composite_alpha.iter() 67 | .find(|mode| { 68 | use CompositeAlpha::*; 69 | 70 | matches!(mode, PreMultiplied | PostMultiplied) 71 | }) 72 | .expect("Surface does not support transparency"); 73 | 74 | let transform = capabilities.current_transform; 75 | 76 | let framebuffer_count = capabilities.min_image_count; 77 | 78 | let surface = Self { 79 | surface, 80 | format, 81 | alpha_mode, 82 | transform, 83 | framebuffer_count, 84 | }; 85 | 86 | (surface, device) 87 | } 88 | } -------------------------------------------------------------------------------- /src/graphics/swapchain.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use vulkano::command_buffer::*; 4 | use vulkano::command_buffer::allocator::StandardCommandBufferAlloc; 5 | use vulkano::image::{ImageUsage, ImageAccess}; 6 | use vulkano::image::{view::ImageView, SwapchainImage}; 7 | use vulkano::pipeline::PipelineBindPoint; 8 | use vulkano::pipeline::graphics::viewport::Viewport; 9 | use vulkano::render_pass::{LoadOp, StoreOp}; 10 | use vulkano::swapchain::{Swapchain as VkSwapchain, SwapchainCreateInfo, SwapchainPresentInfo, SwapchainAcquireFuture}; 11 | 12 | use super::pipeline::Pipeline; 13 | use super::surface::Surface; 14 | use super::device::Device; 15 | use super::vertex::VisualiserVertexVec; 16 | use super::sampler::Sampler; 17 | 18 | struct Framebuffer { 19 | attachment_image: Arc>, 20 | command_buffer: Arc>, 21 | } 22 | 23 | impl Framebuffer { 24 | fn create( 25 | image: Arc, 26 | device: &Device, 27 | pipeline: &Pipeline, 28 | sampler: &Sampler, 29 | vertices: &VisualiserVertexVec, 30 | viewport: Viewport, 31 | ) -> Self { 32 | let attachment_image = ImageView::new_default(image).unwrap(); 33 | 34 | let command_buffer = Self::create_command_buffer( 35 | pipeline, 36 | device, 37 | &attachment_image, 38 | sampler, 39 | vertices, 40 | viewport 41 | ); 42 | 43 | Self { attachment_image, command_buffer } 44 | } 45 | 46 | fn create_command_buffer( 47 | pipeline: &Pipeline, 48 | device: &Device, 49 | attachment_image: &Arc>, 50 | sampler: &Sampler, 51 | vertices: &VisualiserVertexVec, 52 | viewport: Viewport, 53 | ) -> Arc> { 54 | let mut builder = AutoCommandBufferBuilder::primary( 55 | &device.command_buffer_allocator, 56 | device.queue_family_index, 57 | CommandBufferUsage::MultipleSubmit, 58 | ).unwrap(); 59 | 60 | builder 61 | .copy_buffer_to_image(sampler.copy_operation()).unwrap() 62 | .begin_rendering(RenderingInfo { 63 | color_attachments: vec![Some(RenderingAttachmentInfo { 64 | load_op: LoadOp::Clear, 65 | store_op: StoreOp::Store, 66 | clear_value: Some([0.0; 4].into()), 67 | ..RenderingAttachmentInfo::image_view(attachment_image.clone()) 68 | })], 69 | ..Default::default() 70 | }).unwrap() 71 | .set_viewport(0, [viewport]) 72 | .bind_pipeline_graphics(pipeline.into()) 73 | .bind_vertex_buffers(0, vertices.position_buffer(&device.memory_allocator)) 74 | .bind_vertex_buffers(1, vertices.frequency_buffer(&device.memory_allocator)) 75 | .bind_vertex_buffers(2, vertices.amplitude_buffer(&device.memory_allocator)) 76 | .bind_descriptor_sets( 77 | PipelineBindPoint::Graphics, 78 | pipeline.layout(), 79 | 0, 80 | vec![sampler.descriptor_set(device, pipeline.into())], 81 | ) 82 | .draw(vertices.len() as u32, 1, 0, 0).unwrap() 83 | .end_rendering().unwrap(); 84 | 85 | builder.build().map(Arc::new).unwrap() 86 | } 87 | } 88 | 89 | pub(crate) struct Swapchain { 90 | swapchain: Arc, 91 | framebuffers: Box<[Framebuffer]>, 92 | pipeline: Pipeline, 93 | viewport: Viewport, 94 | } 95 | 96 | impl Swapchain { 97 | pub fn next(&self) -> (SwapchainAcquireFuture, SwapchainPresentInfo, Arc) { 98 | let (index, suboptimal, acquire_future) = vulkano::swapchain::acquire_next_image( 99 | Arc::clone(&self.swapchain), 100 | None, 101 | ).unwrap(); // TODO: handle AcquireError::OutOfDate 102 | 103 | let present_info = SwapchainPresentInfo::swapchain_image_index(Arc::clone(&self.swapchain), index); 104 | let command_buffer = Arc::clone(&self.framebuffers[index as usize].command_buffer); 105 | 106 | (acquire_future, present_info, command_buffer) 107 | } 108 | 109 | pub fn new( 110 | device: &Device, 111 | surface: &Surface, 112 | sampler: &Sampler, 113 | vertices: &VisualiserVertexVec, 114 | extent: [u32; 2], 115 | ) -> Self { 116 | let (swapchain, swapchain_images) = VkSwapchain::new( 117 | device.into(), 118 | surface.into(), 119 | SwapchainCreateInfo { 120 | min_image_count: surface.framebuffer_count, 121 | image_format: Some(surface.format), 122 | image_extent: extent, 123 | image_usage: ImageUsage { 124 | color_attachment: true, 125 | .. ImageUsage::empty() 126 | }, 127 | pre_transform: surface.transform, 128 | composite_alpha: surface.alpha_mode, 129 | ..Default::default() 130 | }, 131 | ).expect("Failed to create swapchain"); 132 | 133 | 134 | let mut viewport = Viewport { 135 | origin: [0.0, 0.0], 136 | dimensions: [0.0, 0.0], 137 | depth_range: 0.0..1.0, 138 | }; 139 | 140 | let dimensions = swapchain_images.first().unwrap() 141 | .dimensions() 142 | .width_height(); 143 | 144 | viewport.dimensions = [dimensions[0] as f32, dimensions[1] as f32]; 145 | 146 | let pipeline = Pipeline::new(device.into(), surface.format); 147 | 148 | let framebuffers = swapchain_images.into_iter() 149 | .map(|image| Framebuffer::create( 150 | image, 151 | device, 152 | &pipeline, 153 | sampler, 154 | vertices, 155 | viewport.clone() 156 | )) 157 | .collect::>() 158 | .into_boxed_slice(); 159 | 160 | Self { swapchain, framebuffers, pipeline, viewport } 161 | } 162 | } -------------------------------------------------------------------------------- /src/graphics/vertex.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use ahash::{HashMap, HashMapExt}; 4 | 5 | use soa_derive::StructOfArray; 6 | use vulkano::buffer::{CpuAccessibleBuffer, BufferUsage}; 7 | use vulkano::memory::allocator::MemoryAllocator; 8 | use vulkano::pipeline::graphics::vertex_input::*; 9 | use vulkano::format::Format; 10 | 11 | #[derive(StructOfArray)] 12 | pub(crate) struct VisualiserVertex { 13 | pub position: [f32; 2], 14 | pub frequency: f32, 15 | pub amplitude: f32, 16 | } 17 | 18 | impl VisualiserVertexVec { 19 | pub fn position_buffer( 20 | &self, 21 | memory_allocator: &impl MemoryAllocator, 22 | ) -> Arc> { 23 | CpuAccessibleBuffer::from_iter( 24 | memory_allocator, 25 | BufferUsage { 26 | vertex_buffer: true, 27 | ..BufferUsage::empty() 28 | }, 29 | false, 30 | self.position.clone(), 31 | ).unwrap() 32 | } 33 | 34 | pub fn frequency_buffer( 35 | &self, 36 | memory_allocator: &impl MemoryAllocator, 37 | ) -> Arc> { 38 | CpuAccessibleBuffer::from_iter( 39 | memory_allocator, 40 | BufferUsage { 41 | vertex_buffer: true, 42 | ..BufferUsage::empty() 43 | }, 44 | false, 45 | self.frequency.clone(), 46 | ).unwrap() 47 | } 48 | 49 | pub fn amplitude_buffer( 50 | &self, 51 | memory_allocator: &impl MemoryAllocator, 52 | ) -> Arc> { 53 | CpuAccessibleBuffer::from_iter( 54 | memory_allocator, 55 | BufferUsage { 56 | vertex_buffer: true, 57 | ..BufferUsage::empty() 58 | }, 59 | false, 60 | self.amplitude.clone(), 61 | ).unwrap() 62 | } 63 | } 64 | 65 | pub(crate) trait Vertex { 66 | fn input_state() -> VertexInputState; 67 | //fn attributes(index: u32) -> Iterator; 68 | } 69 | 70 | impl Vertex for VisualiserVertex { 71 | fn input_state() -> VertexInputState { 72 | let mut vertex_bindings = HashMap::new(); 73 | vertex_bindings.insert(0, VertexInputBindingDescription { 74 | stride: 8, 75 | input_rate: VertexInputRate::Vertex, 76 | }); 77 | vertex_bindings.insert(1, VertexInputBindingDescription { 78 | stride: 4, 79 | input_rate: VertexInputRate::Vertex, 80 | }); 81 | vertex_bindings.insert(2, VertexInputBindingDescription { 82 | stride: 4, 83 | input_rate: VertexInputRate::Vertex, 84 | }); 85 | 86 | let mut vertex_attributes = HashMap::new(); 87 | vertex_attributes.insert(0, VertexInputAttributeDescription { 88 | binding: 0, 89 | format: Format::R32G32_SFLOAT, 90 | offset: 0, 91 | }); 92 | vertex_attributes.insert(1, VertexInputAttributeDescription { 93 | binding: 1, 94 | format: Format::R32_SFLOAT, 95 | offset: 0, 96 | }); 97 | vertex_attributes.insert(2, VertexInputAttributeDescription { 98 | binding: 2, 99 | format: Format::R32_SFLOAT, 100 | offset: 0, 101 | }); 102 | 103 | VertexInputState { 104 | bindings: vertex_bindings, 105 | attributes: vertex_attributes, 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(once_cell)] 2 | 3 | mod graphics; 4 | mod window; 5 | mod audio; 6 | mod visualiser; 7 | 8 | use std::{sync::{Arc, RwLock}, path::PathBuf}; 9 | 10 | use window::Window; 11 | use visualiser::BufferManager; 12 | 13 | const BUFFER_SIZE: usize = 512; 14 | 15 | use clap::Parser; 16 | 17 | #[derive(Debug, Parser)] 18 | struct Arguments { 19 | /// Shift the spectrum to show more detail at the lower frequencies at 20 | /// values greater than 1 and higher frequencies at less than 1. 21 | #[arg(short, long, default_value_t = 1.02)] 22 | power_scale_frequencies: f32, 23 | /// Clip the spectrum to have this frequency be the highest pitch 24 | #[arg(short, long, default_value_t = 15000.0)] 25 | ceiling_frequency: f32, 26 | /// Clip the spectrum to have this frequency be the lowest pitch 27 | #[arg(short, long, default_value_t = 0.0)] 28 | floor_frequency: f32, 29 | /// Multiply the output levels by the value 30 | #[arg(short, long, default_value_t = 1.0)] 31 | scale: f32, 32 | /// Path to the obj file to use for displaying data 33 | layout: Option, 34 | } 35 | 36 | lazy_static::lazy_static! { 37 | static ref CONFIG: Arguments = Arguments::parse(); 38 | } 39 | 40 | fn main() { 41 | let buffer_manager = Arc::new(RwLock::new(BufferManager::default())); 42 | 43 | audio::main(Arc::clone(&buffer_manager)); 44 | 45 | let mut window = Window::new(buffer_manager); 46 | window.run(); 47 | } 48 | -------------------------------------------------------------------------------- /src/shaders/basic.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (binding = 0) uniform sampler1D frequency_magnitude; 4 | 5 | layout (location = 0) in float frag_frequency; 6 | layout (location = 1) in float target_amplitude; 7 | 8 | layout (location = 0) out vec4 color; 9 | 10 | 11 | vec3 hsv_to_rgb(float h) { 12 | vec3 hsv = vec3(h, 1.0, 1.0); 13 | 14 | vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); 15 | vec3 p = abs(fract(hsv.xxx + K.xyz) * 6.0 - K.www); 16 | return hsv.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsv.y); 17 | } 18 | 19 | void main() { 20 | float amplitude = texture(frequency_magnitude, frag_frequency).r; 21 | 22 | if (amplitude > target_amplitude) { 23 | color = vec4(hsv_to_rgb(frag_frequency), 1.0); 24 | } else { 25 | color = vec4(0.0, 0.0, 0.0, 0.0); 26 | } 27 | } -------------------------------------------------------------------------------- /src/shaders/basic.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (binding = 0) uniform sampler1D frequency_magnitude; 4 | 5 | layout (location = 0) in vec2 vertex_position; 6 | layout (location = 1) in float vertex_frequency; 7 | layout (location = 2) in float vertex_amplitude; 8 | 9 | layout (location = 0) out float frag_frequency; 10 | layout (location = 1) out float target_amplitude; 11 | 12 | void main() { 13 | frag_frequency = vertex_frequency; 14 | target_amplitude = vertex_amplitude; 15 | gl_Position = vec4(vertex_position, 0.0, 1.0); 16 | } -------------------------------------------------------------------------------- /src/shaders/frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlankParenthesis/visualiser/c447459adc809f94f31ccc58189a5400c5ff8862/src/shaders/frag.spv -------------------------------------------------------------------------------- /src/shaders/vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlankParenthesis/visualiser/c447459adc809f94f31ccc58189a5400c5ff8862/src/shaders/vert.spv -------------------------------------------------------------------------------- /src/visualiser.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | use std::collections::{VecDeque, HashMap}; 3 | 4 | use enterpolation::{linear::Linear, Curve}; 5 | use rustfft::{FftDirection, Fft}; 6 | use rustfft::algorithm::Radix4; 7 | use rustfft::num_complex::Complex; 8 | 9 | use crate::CONFIG; 10 | 11 | const BUFFER_TARGET: usize = 3; 12 | 13 | struct AudioBuffer { 14 | data: Box<[f32]>, 15 | position: usize, 16 | rate: f32, 17 | } 18 | 19 | impl AudioBuffer { 20 | fn read(&mut self, duration: Duration) -> (&[f32], Duration) { 21 | let desired_read_count = (duration.as_secs_f32() * self.rate).floor() as usize; 22 | 23 | let max_read_count = self.data.len() - self.position; 24 | let values_to_read = usize::min(max_read_count, desired_read_count); 25 | 26 | let elapsed = Duration::from_secs_f32((values_to_read) as f32 / self.rate); 27 | let next_position = self.position + values_to_read; 28 | 29 | let data = &self.data[self.position..next_position]; 30 | 31 | self.position = next_position; 32 | 33 | (data, elapsed) 34 | } 35 | } 36 | 37 | struct FftCache { 38 | algorithm: Radix4, 39 | window: Box<[f32]>, 40 | scaling_factor: f32, 41 | } 42 | 43 | #[derive(Default)] 44 | pub(crate) struct BufferManager { 45 | buffers: VecDeque, 46 | /// key is the power to raise 2 to for the radix size 47 | ffts: HashMap, 48 | } 49 | 50 | struct BufferSlice { 51 | values: Vec, 52 | rate: f32, 53 | } 54 | 55 | impl BufferManager { 56 | fn take_next(&mut self, interval: Duration) -> BufferSlice { 57 | let mut values = Vec::new(); 58 | let mut buffers_taken = 0; 59 | let mut rate = 0.0; 60 | let mut remaining_interval = interval; 61 | let interval = interval.as_secs_f32(); 62 | 63 | for buffer in &mut self.buffers { 64 | let buffer_rate = buffer.rate; 65 | let (slice, elapsed) = buffer.read(remaining_interval); 66 | 67 | rate += buffer_rate * elapsed.as_secs_f32() / interval; 68 | 69 | values.extend_from_slice(slice); 70 | remaining_interval = remaining_interval.saturating_sub(elapsed); 71 | 72 | // why not is_zero?: because floating point imprecision and rounding 73 | if remaining_interval.as_millis() < 1 { 74 | break; 75 | } 76 | 77 | buffers_taken += 1; 78 | } 79 | 80 | // to account for any remaining time, scale up the existing rate 81 | let total_elapsed = interval - remaining_interval.as_secs_f32(); 82 | rate /= total_elapsed / interval; 83 | 84 | self.buffers.drain(0..buffers_taken); 85 | 86 | BufferSlice { values, rate } 87 | } 88 | 89 | // TODO: would be nice to have constant_q and/or variable_q intervals 90 | 91 | pub fn fft_interval( 92 | &mut self, 93 | interval: Duration, 94 | ) -> Option> { 95 | let BufferSlice { values, rate } = self.take_next(interval); 96 | 97 | if values.len() < 2 { 98 | return None; 99 | } 100 | 101 | let power_of_2 = f32::log2(values.len() as f32).floor() as u32; 102 | let size = 2_u32.pow(power_of_2) as usize; 103 | 104 | let fft = self.ffts.entry(power_of_2 as u8).or_insert_with(|| { 105 | FftCache { 106 | algorithm: Radix4::new(size, FftDirection::Forward), 107 | window: apodize::hamming_iter(size).map(|v| v as f32).collect(), 108 | scaling_factor: (size as f32).sqrt(), 109 | } 110 | }); 111 | 112 | let mut truncated_data = values[0..size].iter() 113 | .cloned() 114 | .zip(fft.window.iter()) 115 | .map(|(val, scale)| Complex { re: val * scale, im: 0.0 }) 116 | .collect::>(); 117 | 118 | fft.algorithm.process(truncated_data.as_mut_slice()); 119 | 120 | // NOTE: taking anything > rate/2 results in Hermitian symmetry 121 | let max_frequency_ratio = CONFIG.ceiling_frequency / rate; 122 | let min_frequency_ratio = CONFIG.floor_frequency / rate; 123 | let max_index = usize::min(size, (size as f32 * max_frequency_ratio) as usize); 124 | let min_index = (size as f32 * min_frequency_ratio) as usize; 125 | 126 | let range = min_index..max_index; 127 | let count = range.len(); 128 | 129 | if count < 2 { 130 | // enterpolation needs at least two values 131 | return None; 132 | } 133 | 134 | fn power_range(base: f32, count: usize) -> Box<[f32]> { 135 | let power_data = (0..(count - 1)) 136 | .map(|power| 1.0 - 1.0 / base.powf(power as f32)); 137 | 138 | [0.0].into_iter().chain(power_data).collect() 139 | } 140 | 141 | Some(Linear::builder() 142 | .elements(&truncated_data[range]) 143 | .knots(power_range(CONFIG.power_scale_frequencies, count).as_ref()) 144 | //.equidistant::() 145 | //.normalized() 146 | .build() 147 | .unwrap() 148 | .take(T) 149 | .map(|Complex { re, im }| { 150 | let power = f32::sqrt(re * re + im * im); 151 | let value = power / fft.scaling_factor; 152 | let log_scale = f32::log10(1.0 + value); 153 | 154 | log_scale * CONFIG.scale 155 | }) 156 | .collect::>() 157 | .try_into().unwrap()) 158 | } 159 | 160 | pub fn fill_buffer(&mut self, buffer: &[f32], rate: u32) { 161 | if self.buffers.len() >= BUFFER_TARGET { 162 | // render thread is behind (or not drawing) 163 | // pause as to not waste resources copying data 164 | return; 165 | } 166 | 167 | self.buffers.push_back(AudioBuffer { 168 | position: 0, 169 | rate: rate as f32, 170 | data: Vec::from(buffer).into_boxed_slice(), 171 | }); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{RwLock, Arc}; 2 | use std::time::Duration; 3 | 4 | use wayland_client::{Connection, Dispatch, Proxy, QueueHandle, EventQueue}; 5 | use wayland_client::protocol::wl_surface::{self, WlSurface}; 6 | use wayland_client::protocol::wl_registry::{self, WlRegistry}; 7 | use wayland_client::protocol::wl_compositor::{self, WlCompositor}; 8 | use wayland_client::protocol::wl_callback::{self, WlCallback}; 9 | use wayland_client::protocol::wl_display::WlDisplay; 10 | use wayland_client::protocol::wl_region::{self, WlRegion}; 11 | use wayland_protocols::xdg::shell::client::xdg_surface::{self, XdgSurface}; 12 | use wayland_protocols::xdg::shell::client::xdg_toplevel::{self, XdgToplevel}; 13 | use wayland_protocols::xdg::shell::client::xdg_wm_base::{self, XdgWmBase}; 14 | 15 | use crate::BUFFER_SIZE; 16 | use crate::graphics::Graphics; 17 | use crate::visualiser::BufferManager; 18 | 19 | struct GraphicsState { 20 | surface: XdgSurface, 21 | toplevel: XdgToplevel, 22 | graphics: Graphics, 23 | } 24 | 25 | pub(crate) struct Window { 26 | running: bool, 27 | display: WlDisplay, 28 | event_queue: Option>, 29 | wm_base: Option, 30 | base_surface: Option, 31 | graphics_state: Option, 32 | configured: bool, 33 | visualiser: Arc>, 34 | last_frame: u32, 35 | } 36 | 37 | impl Window { 38 | pub fn new(visualiser: Arc>) -> Self { 39 | let connection = Connection::connect_to_env().unwrap(); 40 | 41 | let event_queue = connection.new_event_queue(); 42 | let queue_handle = event_queue.handle(); 43 | 44 | let display = connection.display(); 45 | display.get_registry(&queue_handle, ()); 46 | 47 | Window { 48 | running: false, 49 | display, 50 | event_queue: Some(event_queue), 51 | wm_base: None, 52 | base_surface: None, 53 | graphics_state: None, 54 | configured: false, 55 | visualiser, 56 | last_frame: 0, 57 | } 58 | } 59 | 60 | pub fn run(&mut self) { 61 | self.running = true; 62 | 63 | let mut event_queue = self.event_queue.take().unwrap(); 64 | 65 | while self.running { 66 | event_queue.blocking_dispatch(self).unwrap(); 67 | } 68 | } 69 | 70 | fn init_xdg_surface(&mut self, queue_handle: &QueueHandle) { 71 | let wm_base = self.wm_base.as_ref().unwrap(); 72 | let base_surface = self.base_surface.as_ref().unwrap(); 73 | 74 | let xdg_surface = wm_base.get_xdg_surface(base_surface, queue_handle, ()); 75 | let toplevel = xdg_surface.get_toplevel(queue_handle, ()); 76 | toplevel.set_title("Visualiser".into()); 77 | toplevel.set_app_id(env!("CARGO_PKG_NAME").into()); 78 | 79 | let graphics = Graphics::new(&self.display, base_surface, [600, 400]); 80 | 81 | base_surface.commit(); 82 | base_surface.frame(queue_handle, ()); 83 | 84 | self.graphics_state = Some(GraphicsState { 85 | surface: xdg_surface, 86 | toplevel, 87 | graphics, 88 | }); 89 | } 90 | } 91 | 92 | impl Dispatch for Window { 93 | fn event( 94 | state: &mut Self, 95 | registry: &WlRegistry, 96 | event: ::Event, 97 | _data: &(), 98 | _conn: &Connection, 99 | queue_handle: &QueueHandle, 100 | ) { 101 | match event { 102 | wl_registry::Event::Global { name, interface, version } 103 | if interface.as_str() == "wl_compositor" => { 104 | let compositor = registry.bind::(name, version, queue_handle, ()); 105 | 106 | let surface = compositor.create_surface(queue_handle, ()); 107 | 108 | let region = compositor.create_region(queue_handle, ()); 109 | surface.set_input_region(Some(®ion)); 110 | 111 | let previous_surface = state.base_surface.replace(surface); 112 | assert!(previous_surface.is_none()); 113 | 114 | if state.wm_base.is_some() && state.graphics_state.is_none() { 115 | state.init_xdg_surface(queue_handle); 116 | } 117 | }, 118 | wl_registry::Event::Global { name, interface, version } 119 | if interface.as_str() == "xdg_wm_base" => { 120 | let wm_base = registry.bind::(name, version, queue_handle, ()); 121 | let previous_base = state.wm_base.replace(wm_base); 122 | assert!(previous_base.is_none()); 123 | 124 | if state.base_surface.is_some() && state.graphics_state.is_none() { 125 | state.init_xdg_surface(queue_handle); 126 | } 127 | }, 128 | _ => {}, 129 | } 130 | } 131 | } 132 | 133 | impl Dispatch for Window { 134 | fn event( 135 | _: &mut Self, 136 | _: &WlCompositor, 137 | event: wl_compositor::Event, 138 | _: &(), 139 | _: &Connection, 140 | _: &QueueHandle, 141 | ) { 142 | unimplemented!("wl_compositor unknown event: {:?}", event) 143 | } 144 | } 145 | 146 | impl Dispatch for Window { 147 | fn event( 148 | _: &mut Self, 149 | _: &WlSurface, 150 | _: wl_surface::Event, 151 | _: &(), 152 | _: &Connection, 153 | _: &QueueHandle, 154 | ) {} 155 | } 156 | 157 | impl Dispatch for Window { 158 | fn event( 159 | _: &mut Self, 160 | base: &XdgWmBase, 161 | event: xdg_wm_base::Event, 162 | _: &(), 163 | _: &Connection, 164 | _: &QueueHandle, 165 | ) { 166 | match event { 167 | xdg_wm_base::Event::Ping { serial } => { 168 | base.send_request(xdg_wm_base::Request::Pong { serial }).unwrap(); 169 | }, 170 | event => unimplemented!("xdg_wm_base unknown event: {:?}", event) 171 | } 172 | } 173 | } 174 | 175 | impl Dispatch for Window { 176 | fn event( 177 | _: &mut Self, 178 | surface: &XdgSurface, 179 | event: xdg_surface::Event, 180 | _: &(), 181 | _: &Connection, 182 | _: &QueueHandle, 183 | ) { 184 | match event { 185 | xdg_surface::Event::Configure { serial } => { 186 | println!("configure_surface: {}", serial); 187 | // TODO: actually configure 188 | surface.send_request(xdg_surface::Request::AckConfigure { serial }).unwrap() 189 | }, 190 | event => unimplemented!("xdg_wm_base unknown event: {:?}", event) 191 | } 192 | } 193 | } 194 | 195 | impl Dispatch for Window { 196 | fn event( 197 | state: &mut Self, 198 | toplevel: &XdgToplevel, 199 | event: xdg_toplevel::Event, 200 | _: &(), 201 | _: &Connection, 202 | _: &QueueHandle, 203 | ) { 204 | match event { 205 | xdg_toplevel::Event::ConfigureBounds { width, height } => { 206 | println!("max_size: {}×{}", width, height); 207 | }, 208 | xdg_toplevel::Event::Configure { states, width, height } 209 | if width == 0 && height == 0 => { 210 | if !state.configured { 211 | // TODO: do the configuring 212 | state.graphics_state.as_mut().unwrap().graphics.draw(Some(Box::new([0.0; BUFFER_SIZE]))); 213 | println!("configure"); 214 | state.configured = true; 215 | } else { 216 | // TODO: do a reconfigure 217 | } 218 | println!("self configure: {:?}", states); 219 | }, 220 | xdg_toplevel::Event::Configure { states, width, height } => { 221 | println!("configure: {}×{}, {:?}", width, height, states); 222 | }, 223 | xdg_toplevel::Event::Close => { 224 | state.running = false; 225 | }, 226 | xdg_toplevel::Event::WmCapabilities { capabilities: _ } => {}, 227 | event => unimplemented!("xdg_toplevel unknown event: {:?}", event) 228 | } 229 | } 230 | } 231 | 232 | impl Dispatch for Window { 233 | fn event( 234 | state: &mut Self, 235 | _: &WlCallback, 236 | event: wl_callback::Event, 237 | _: &(), 238 | _: &Connection, 239 | queue_handle: &QueueHandle, 240 | ) { 241 | match event { 242 | wl_callback::Event::Done { callback_data } => { 243 | let surface = state.base_surface.as_ref().unwrap(); 244 | let interval = Duration::from_millis((callback_data - state.last_frame) as u64); 245 | state.last_frame = callback_data; 246 | 247 | surface.frame(queue_handle, ()); 248 | 249 | let data = state.visualiser.write().unwrap() 250 | .fft_interval(interval); 251 | 252 | state.graphics_state.as_mut().unwrap().graphics.draw(data); 253 | }, 254 | event => unimplemented!("wl_callback unknown event: {:?}", event) 255 | } 256 | } 257 | } 258 | 259 | impl Dispatch for Window { 260 | fn event( 261 | _: &mut Self, 262 | _: &WlRegion, 263 | event: wl_region::Event, 264 | _: &(), 265 | _: &Connection, 266 | _: &QueueHandle, 267 | ) { 268 | unimplemented!("wl_region unknown event: {:?}", event) 269 | } 270 | } 271 | --------------------------------------------------------------------------------