From f96269781fbc4cd64ec7bfe2dca5aa9fb1aa8d03 Mon Sep 17 00:00:00 2001 From: MyBlackMIDIScore Date: Tue, 1 Oct 2024 00:29:22 +0300 Subject: [PATCH] UI remake initial commit --- .gitignore | 2 +- Cargo.lock | 2219 ++++++++++++----- Cargo.toml | 78 +- assets/Poppins-Bold.ttf | Bin 0 -> 153944 bytes assets/Poppins-Medium.ttf | Bin 0 -> 156520 bytes assets/UbuntuSansMono-Medium.ttf | Bin 0 -> 118836 bytes assets/folder.svg | 41 + assets/logo.svg | 493 ++++ assets/logo_16.svg | 493 ++++ assets/options.svg | 53 + assets/pause.svg | 54 + assets/pin.svg | 44 + assets/play.svg | 45 + assets/stop.svg | 48 + src/audio_playback/empty.rs | 23 + src/audio_playback/kdmapi.rs | 40 + src/audio_playback/midiout.rs | 60 + src/audio_playback/mod.rs | 137 +- src/audio_playback/xsynth.rs | 88 +- src/gui/window.rs | 356 ++- src/gui/window/about.rs | 152 ++ src/gui/window/fps.rs | 3 +- src/gui/window/playback_panel.rs | 220 ++ src/gui/window/scene.rs | 5 +- src/gui/window/scene/cake_system/mod.rs | 190 +- src/gui/window/scene/note_list_system/mod.rs | 6 +- .../note_list_system/notes_render_pass.rs | 204 +- src/gui/window/settings.rs | 181 ++ src/gui/window/settings/midi.rs | 149 ++ src/gui/window/settings/soundfonts.rs | 22 + src/gui/window/settings/soundfonts/cfg.rs | 122 + src/gui/window/settings/soundfonts/list.rs | 391 +++ src/gui/window/settings/synth.rs | 96 + src/gui/window/settings/synth/kdmapi.rs | 23 + src/gui/window/settings/synth/mididevice.rs | 66 + src/gui/window/settings/synth/xsynth.rs | 102 + src/gui/window/settings/visual.rs | 175 ++ src/gui/window/settings_window.rs | 186 -- src/gui/window/shortcuts.rs | 60 + src/gui/window/stats.rs | 164 +- src/gui/window/top_panel.rs | 90 - src/gui/window/xsynth_settings.rs | 174 -- src/main.rs | 149 +- src/midi/audio/live.rs | 6 +- src/midi/audio/ram.rs | 6 +- src/midi/cake/mod.rs | 14 +- src/midi/live/mod.rs | 16 +- src/midi/live/parse.rs | 4 +- src/midi/live/view.rs | 8 +- src/midi/mod.rs | 27 +- src/midi/ram/parse.rs | 12 +- src/midi/ram/view.rs | 8 +- src/renderer.rs | 56 +- src/renderer/swapchain.rs | 47 +- src/scenes.rs | 17 +- src/settings/enums.rs | 167 ++ src/settings/migrations.rs | 81 +- src/settings/migrations/serializers.rs | 104 + src/settings/migrations/v0.rs | 71 + src/settings/migrations/v1.rs | 101 + src/settings/mod.rs | 703 ++---- src/state.rs | 46 +- src/utils.rs | 19 + 63 files changed, 6337 insertions(+), 2380 deletions(-) create mode 100644 assets/Poppins-Bold.ttf create mode 100644 assets/Poppins-Medium.ttf create mode 100644 assets/UbuntuSansMono-Medium.ttf create mode 100644 assets/folder.svg create mode 100644 assets/logo.svg create mode 100644 assets/logo_16.svg create mode 100644 assets/options.svg create mode 100644 assets/pause.svg create mode 100644 assets/pin.svg create mode 100644 assets/play.svg create mode 100644 assets/stop.svg create mode 100644 src/audio_playback/empty.rs create mode 100644 src/audio_playback/kdmapi.rs create mode 100644 src/audio_playback/midiout.rs create mode 100644 src/gui/window/about.rs create mode 100644 src/gui/window/playback_panel.rs create mode 100644 src/gui/window/settings.rs create mode 100644 src/gui/window/settings/midi.rs create mode 100644 src/gui/window/settings/soundfonts.rs create mode 100644 src/gui/window/settings/soundfonts/cfg.rs create mode 100644 src/gui/window/settings/soundfonts/list.rs create mode 100644 src/gui/window/settings/synth.rs create mode 100644 src/gui/window/settings/synth/kdmapi.rs create mode 100644 src/gui/window/settings/synth/mididevice.rs create mode 100644 src/gui/window/settings/synth/xsynth.rs create mode 100644 src/gui/window/settings/visual.rs delete mode 100644 src/gui/window/settings_window.rs create mode 100644 src/gui/window/shortcuts.rs delete mode 100644 src/gui/window/top_panel.rs delete mode 100644 src/gui/window/xsynth_settings.rs create mode 100644 src/settings/enums.rs create mode 100644 src/settings/migrations/serializers.rs create mode 100644 src/settings/migrations/v0.rs create mode 100644 src/settings/migrations/v1.rs diff --git a/.gitignore b/.gitignore index dc84dae..386c146 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /target wasabi_config.toml .direnv -.vscode \ No newline at end of file +.vscode diff --git a/Cargo.lock b/Cargo.lock index b1021f7..7708fb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,16 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +[[package]] +name = "accesskit" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef7442f1f520649b8e11ee3af6caeec24123fed4b63bc36a85b67308d8514fdf" +dependencies = [ + "enumn", + "serde", +] + [[package]] name = "adler" version = "1.0.2" @@ -33,6 +43,7 @@ dependencies = [ "cfg-if", "getrandom", "once_cell", + "serde", "version_check", "zerocopy", ] @@ -46,6 +57,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "alsa" version = "0.9.0" @@ -86,58 +103,37 @@ dependencies = [ ] [[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - -[[package]] -name = "anstream" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" - -[[package]] -name = "anstyle-parse" -version = "0.2.3" +name = "android-activity" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ - "utf8parse", + "android-properties", + "bitflags 2.5.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk 0.9.0", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum 0.7.3", + "thiserror", ] [[package]] -name = "anstyle-query" -version = "1.0.2" +name = "android-properties" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" -dependencies = [ - "windows-sys 0.52.0", -] +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] -name = "anstyle-wincon" -version = "3.0.2" +name = "anyhow" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "approx" @@ -148,6 +144,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "arboard" version = "3.3.2" @@ -164,6 +166,17 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -176,6 +189,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + [[package]] name = "ash" version = "0.37.3+1.3.251" @@ -187,139 +206,101 @@ dependencies = [ [[package]] name = "ashpd" -version = "0.6.8" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac22eda5891cc086690cb6fa10121c0390de0e3b04eb269f2d766b00d3f2d81" +checksum = "bfe7e0dd0ac5a401dc116ed9f9119cf9decc625600474cb41f0fc0a0050abc9a" dependencies = [ - "async-fs 2.1.1", + "async-fs", "async-net", "enumflags2", "futures-channel", "futures-util", - "once_cell", "rand", + "raw-window-handle 0.6.2", "serde", "serde_repr", "url", + "wayland-backend", + "wayland-client 0.31.6", + "wayland-protocols 0.32.4", "zbus", ] [[package]] name = "async-broadcast" -version = "0.5.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" dependencies = [ - "event-listener 2.5.3", + "event-listener", + "event-listener-strategy", "futures-core", + "pin-project-lite", ] [[package]] name = "async-channel" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener 5.2.0", - "event-listener-strategy 0.5.1", + "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.9.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b3e585719c2358d2660232671ca8ca4ddb4be4ce8a1842d6c2dc8685303316" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ - "async-lock 3.3.0", "async-task", "concurrent-queue", - "fastrand 2.0.2", - "futures-lite 2.3.0", + "fastrand", + "futures-lite", "slab", ] [[package]] name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "blocking", - "futures-lite 1.13.0", -] - -[[package]] -name = "async-fs" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc19683171f287921f2405677dd2ed2549c3b3bda697a563ebc3a121ace2aba1" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ - "async-lock 3.3.0", + "async-lock", "blocking", - "futures-lite 2.3.0", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2", - "waker-fn", + "futures-lite", ] [[package]] name = "async-io" -version = "2.3.2" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ - "async-lock 3.3.0", + "async-lock", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite", "parking", - "polling 3.6.0", - "rustix 0.38.32", + "polling", + "rustix", "slab", "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", + "windows-sys 0.59.0", ] [[package]] name = "async-lock" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", + "event-listener", + "event-listener-strategy", "pin-project-lite", ] @@ -329,33 +310,35 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ - "async-io 2.3.2", + "async-io", "blocking", - "futures-lite 2.3.0", + "futures-lite", ] [[package]] name = "async-process" -version = "1.8.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ - "async-io 1.13.0", - "async-lock 2.8.0", + "async-channel", + "async-io", + "async-lock", "async-signal", + "async-task", "blocking", "cfg-if", - "event-listener 3.1.0", - "futures-lite 1.13.0", - "rustix 0.38.32", - "windows-sys 0.48.0", + "event-listener", + "futures-lite", + "rustix", + "tracing", ] [[package]] name = "async-recursion" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", @@ -364,33 +347,33 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.5" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.2", - "async-lock 2.8.0", + "async-io", + "async-lock", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.32", + "rustix", "signal-hook-registry", "slab", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "async-task" -version = "4.7.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", @@ -405,9 +388,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atomic_float" -version = "0.1.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62af46d040ba9df09edc6528dae9d8e49f5f3e82f55b7d2ec31a733c38dbc49d" +checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" [[package]] name = "atomic_refcell" @@ -432,6 +415,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + [[package]] name = "base64" version = "0.21.7" @@ -453,7 +459,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.58", ] @@ -485,6 +491,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bitstream-io" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" + [[package]] name = "block" version = "0.1.6" @@ -506,7 +518,7 @@ version = "0.1.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" dependencies = [ - "objc-sys", + "objc-sys 0.2.0-beta.2", ] [[package]] @@ -516,36 +528,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" dependencies = [ "block-sys", - "objc2-encode", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", ] [[package]] name = "blocking" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ "async-channel", - "async-lock 3.3.0", "async-task", - "fastrand 2.0.2", "futures-io", - "futures-lite 2.3.0", + "futures-lite", "piper", - "tracing", ] +[[package]] +name = "built" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" + [[package]] name = "bumpalo" version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + [[package]] name = "bytemuck" -version = "1.16.3" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" dependencies = [ "bytemuck_derive", ] @@ -567,6 +597,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.6.0" @@ -587,14 +623,40 @@ dependencies = [ "vec_map", ] +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.5.0", + "log", + "polling", + "rustix", + "slab", + "thiserror", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix", + "wayland-backend", + "wayland-client 0.31.6", +] + [[package]] name = "cbindgen" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49" dependencies = [ - "clap 3.2.25", - "heck", + "clap", + "heck 0.4.1", "indexmap 1.9.3", "log", "proc-macro2", @@ -631,6 +693,16 @@ dependencies = [ "nom", ] +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -643,6 +715,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "clang-sys" version = "1.7.0" @@ -662,34 +740,13 @@ checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags 1.3.2", - "clap_lex 0.2.4", + "clap_lex", "indexmap 1.9.3", - "strsim 0.10.0", + "strsim", "termcolor", "textwrap", ] -[[package]] -name = "clap" -version = "4.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" -dependencies = [ - "anstream", - "anstyle", - "clap_lex 0.7.0", - "strsim 0.11.1", -] - [[package]] name = "clap_lex" version = "0.2.4" @@ -699,12 +756,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "clap_lex" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" - [[package]] name = "clipboard-win" version = "5.3.0" @@ -729,12 +780,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - [[package]] name = "colors-transform" version = "0.2.11" @@ -753,25 +798,13 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] -[[package]] -name = "confy" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37668cb35145dcfaa1931a5f37fde375eeae8068b4c0d2f289da28a270b2d2c" -dependencies = [ - "directories 4.0.1", - "serde", - "thiserror", - "toml 0.5.11", -] - [[package]] name = "convert_case" version = "0.4.0" @@ -803,7 +836,20 @@ dependencies = [ "bitflags 1.3.2", "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.5.0", "libc", ] @@ -838,6 +884,27 @@ dependencies = [ "bindgen", ] +[[package]] +name = "coremidi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964eb3e10ea8b0d29c797086aab3ca730f75e06dced0cb980642fd274a5cca30" +dependencies = [ + "block", + "core-foundation", + "core-foundation-sys", + "coremidi-sys", +] + +[[package]] +name = "coremidi-sys" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709d142e542467e028d5dc5f0374392339ab7dead0c48c129504de2ccd667e1b" +dependencies = [ + "core-foundation-sys", +] + [[package]] name = "cpal" version = "0.15.3" @@ -858,7 +925,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows", + "windows 0.54.0", ] [[package]] @@ -938,6 +1005,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + [[package]] name = "dasp_sample" version = "0.11.0" @@ -950,6 +1023,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "derivative" version = "2.2.0" @@ -971,38 +1050,18 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "directories" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" -dependencies = [ - "dirs-sys 0.3.7", -] - [[package]] name = "directories" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ - "dirs-sys 0.4.1", + "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ @@ -1033,53 +1092,82 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "dpi" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" + [[package]] name = "ecolor" -version = "0.21.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f99fe3cac305af9d6d92971af60d0f7ea4d783201ef1673571567b6699964d9" +checksum = "5629649a8ae57c73f175f4a96419905a8102cfbfcbce96ea25a826bbf468e990" +dependencies = [ + "bytemuck", + "emath", + "serde", +] [[package]] name = "egui" -version = "0.21.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6412a21e0bde7c0918f7fb44bbbb86b5e1f88e63c026a4e747cc7af02f76dfbe" +checksum = "26bab3b3572566257a497b5f87d2cccaf7f7f122d4b8b620cba0493becc7955e" dependencies = [ + "accesskit", "ahash", + "emath", "epaint", + "log", "nohash-hasher", - "tracing", + "serde", ] [[package]] name = "egui-winit" -version = "0.21.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab43597ba41f0ce39a364ad83185594578bfd8b3409b99dbcbb01df23afc3dbb" +checksum = "642c749bf221b5a3ecae3144c98b837729d87b9fde6c39a6ad00f07b71dbee94" dependencies = [ - "android-activity", + "ahash", "arboard", "egui", - "instant", + "log", + "raw-window-handle 0.6.2", "smithay-clipboard", - "tracing", + "web-time", "webbrowser", - "winit", + "winit 0.30.5", ] [[package]] -name = "egui_winit_vulkano" -version = "0.24.0" +name = "egui_extras" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203c1ad521328c178e24d16f5b26ca151a5b1669adafa2c6aa9eb3f904cdfd52" +checksum = "d9f1beb57a3c942fac2f058655188c79ac1cd200555e4f3684cd0c965ceb3a67" +dependencies = [ + "ahash", + "egui", + "enum-map", + "log", + "mime_guess2", + "resvg 0.37.0", +] + +[[package]] +name = "egui_winit_vulkano" +version = "0.27.0" +source = "git+https://github.com/MyBlackMIDIScore/egui_winit_vulkano.git?rev=aa24f97#aa24f979edad909749dc74e78fee5252c650979c" dependencies = [ "ahash", "egui", "egui-winit", "image", + "raw-window-handle 0.6.2", "vulkano", "vulkano-shaders", - "winit", + "winit 0.30.5", ] [[package]] @@ -1090,9 +1178,13 @@ checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "emath" -version = "0.21.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ecd80612937e0267909d5351770fe150004e24dab93954f69ca62eecd3f77e" +checksum = "af86c4efae11da2a3dcbb4afebd0e9ed1916345e8d187b4051d443c8bd79af93" +dependencies = [ + "bytemuck", + "serde", +] [[package]] name = "encoding_rs" @@ -1112,6 +1204,33 @@ dependencies = [ "encoding_rs", ] +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", + "serde", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "enum_dispatch" version = "0.3.13" @@ -1126,9 +1245,9 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" dependencies = [ "enumflags2_derive", "serde", @@ -1136,9 +1255,20 @@ dependencies = [ [[package]] name = "enumflags2_derive" -version = "0.7.9" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "enumn" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", @@ -1147,19 +1277,28 @@ dependencies = [ [[package]] name = "epaint" -version = "0.21.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e78b5c58a1f7f621f9d546add2adce20636422c9b251e29f749e8a2f713c95" +checksum = "445e11ec86a4d85e1350578ba20b2d89977ed937f3faab32e1c3ec81d20c1842" dependencies = [ "ab_glyph", "ahash", - "atomic_refcell", + "bytemuck", "ecolor", "emath", + "epaint_default_fonts", + "log", "nohash-hasher", "parking_lot", + "serde", ] +[[package]] +name = "epaint_default_fonts" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5202b64bef2b2c42a7f6e2e5b40fa83dd04aa61fdb08bfd116553adc149fe47a" + [[package]] name = "equivalent" version = "1.0.1" @@ -1184,60 +1323,22 @@ checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" [[package]] name = "event-listener" -version = "2.5.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - [[package]] name = "event-listener-strategy" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.2.0", + "event-listener", "pin-project-lite", ] @@ -1269,15 +1370,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.0.2" @@ -1324,7 +1416,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", ] [[package]] @@ -1333,6 +1446,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1363,28 +1482,13 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-lite" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.0.2", + "fastrand", "futures-core", "futures-io", "parking", @@ -1512,6 +1616,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1523,9 +1633,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" @@ -1545,7 +1655,8 @@ dependencies = [ [[package]] name = "ico" version = "0.3.0" -source = "git+https://github.com/StratusFearMe21/rust-ico?branch=patch-1#aa5924babb52ee5559cdb3a376d0c060a478c9f1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" dependencies = [ "byteorder", "png", @@ -1563,20 +1674,35 @@ dependencies = [ [[package]] name = "image" -version = "0.24.9" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" dependencies = [ "bytemuck", - "byteorder", + "byteorder-lite", "color_quant", "exr", "gif", - "jpeg-decoder", + "image-webp", "num-traits", "png", "qoi", + "ravif", + "rayon", + "rgb", "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" +dependencies = [ + "byteorder-lite", + "quick-error", ] [[package]] @@ -1585,6 +1711,18 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b72ad49b554c1728b1e83254a1b1565aea4161e28dabbfa171fc15fe62299caf" +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + [[package]] name = "indexmap" version = "1.9.3" @@ -1597,9 +1735,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1607,9 +1745,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", "js-sys", @@ -1618,14 +1756,33 @@ dependencies = [ ] [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "interpolate_name" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", ] [[package]] @@ -1679,9 +1836,6 @@ name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" -dependencies = [ - "rayon", -] [[package]] name = "js-sys" @@ -1757,6 +1911,17 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libloading" version = "0.7.4" @@ -1804,12 +1969,6 @@ dependencies = [ "libc", ] -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -1832,6 +1991,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "mach2" version = "0.4.2" @@ -1850,6 +2018,16 @@ dependencies = [ "libc", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.7.2" @@ -1866,19 +2044,19 @@ dependencies = [ ] [[package]] -name = "memoffset" -version = "0.6.5" +name = "memmap2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ - "autocfg", + "libc", ] [[package]] name = "memoffset" -version = "0.7.1" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -1919,6 +2097,39 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "midir" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe36f39751eb1f449490d4a236e8642c8efee1a87fa81785b063289b689dc84e" +dependencies = [ + "alsa", + "bitflags 1.3.2", + "coremidi", + "js-sys", + "libc", + "parking_lot", + "wasm-bindgen", + "web-sys", + "windows 0.56.0", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess2" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a3333bb1609500601edc766a39b4c1772874a4ce26022f4d866854dc020c41" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1957,7 +2168,7 @@ dependencies = [ "jni-sys", "ndk-sys 0.4.1+23.1.7779620", "num_enum 0.5.11", - "raw-window-handle", + "raw-window-handle 0.5.2", "thiserror", ] @@ -1971,7 +2182,22 @@ dependencies = [ "jni-sys", "log", "ndk-sys 0.5.0+25.2.9519653", - "num_enum 0.7.2", + "num_enum 0.7.3", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.5.0", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum 0.7.3", + "raw-window-handle 0.6.2", "thiserror", ] @@ -1999,6 +2225,21 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nix" version = "0.24.3" @@ -2026,14 +2267,15 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.4" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "cfg-if", + "cfg_aliases 0.1.1", "libc", - "memoffset 0.7.1", + "memoffset 0.9.1", ] [[package]] @@ -2052,6 +2294,22 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -2081,6 +2339,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -2110,11 +2379,11 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ - "num_enum_derive 0.7.2", + "num_enum_derive 0.7.3", ] [[package]] @@ -2142,61 +2411,264 @@ dependencies = [ ] [[package]] -name = "num_enum_derive" -version = "0.7.2" +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.2.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.3.0-beta.3.patch-leaks.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +dependencies = [ + "block2 0.2.0-alpha.6", + "objc-sys 0.2.0-beta.2", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys 0.3.5", + "objc2-encode 4.0.3", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "2.0.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +dependencies = [ + "objc-sys 0.2.0-beta.2", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 2.0.58", + "bitflags 2.5.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", ] [[package]] -name = "objc" -version = "0.2.7" +name = "objc2-quartz-core" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "malloc_buf", + "bitflags 2.5.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", ] [[package]] -name = "objc-foundation" -version = "0.1.1" +name = "objc2-symbols" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ - "block", - "objc", - "objc_id", + "objc2 0.5.2", + "objc2-foundation", ] [[package]] -name = "objc-sys" -version = "0.2.0-beta.2" +name = "objc2-ui-kit" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" - -[[package]] -name = "objc2" -version = "0.3.0-beta.3.patch-leaks.3" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.5.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ - "block2", - "objc-sys", - "objc2-encode", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", ] [[package]] -name = "objc2-encode" -version = "2.0.0-pre.2" +name = "objc2-user-notifications" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "objc-sys", + "bitflags 2.5.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation", ] [[package]] @@ -2237,6 +2709,17 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "open" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -2279,9 +2762,9 @@ dependencies = [ [[package]] name = "palette" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebfc23a4b76642983d57e4ad00bb4504eb30a8ce3c70f4aee1f725610e36d97a" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" dependencies = [ "approx", "fast-srgb8", @@ -2291,10 +2774,11 @@ dependencies = [ [[package]] name = "palette_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8890702dbec0bad9116041ae586f84805b13eecd1d8b1df27c29998a9969d6d" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" dependencies = [ + "by_address", "proc-macro2", "quote", "syn 2.0.58", @@ -2302,9 +2786,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -2335,6 +2819,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2389,6 +2879,26 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -2403,12 +2913,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.0.2", + "fastrand", "futures-io", ] @@ -2433,33 +2943,17 @@ dependencies = [ [[package]] name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.6.0" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.3.9", + "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.32", + "rustix", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2535,6 +3029,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn 2.0.58", +] + [[package]] name = "qoi" version = "0.4.1" @@ -2544,6 +3057,21 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.35" @@ -2583,12 +3111,68 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67376f469e7e7840d0040bbf4b9b3334005bb167f814621326e4c7ab8cd6e944" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + [[package]] name = "rayon" version = "1.10.0" @@ -2713,29 +3297,40 @@ dependencies = [ "log", "pico-args", "rgb", - "svgtypes", - "tiny-skia", - "usvg", + "svgtypes 0.11.0", + "tiny-skia 0.8.4", + "usvg 0.31.0", +] + +[[package]] +name = "resvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" +dependencies = [ + "log", + "pico-args", + "rgb", + "svgtypes 0.13.0", + "tiny-skia 0.11.4", + "usvg 0.37.0", ] [[package]] name = "rfd" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9e7b57df6e8472152674607f6cc68aa14a748a3157a857a94f516e11aeacc2" +checksum = "8af382a047821a08aa6bfc09ab0d80ff48d45d8726f7cd8e44891f7cb4a4278e" dependencies = [ "ashpd", - "async-io 1.13.0", - "block", - "dispatch", - "futures-util", + "block2 0.5.1", "js-sys", "log", - "objc", - "objc-foundation", - "objc_id", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", "pollster", - "raw-window-handle", + "raw-window-handle 0.6.2", "urlencoding", "wasm-bindgen", "wasm-bindgen-futures", @@ -2768,7 +3363,7 @@ dependencies = [ "roxmltree 0.18.1", "simplecss", "siphasher", - "svgtypes", + "svgtypes 0.11.0", ] [[package]] @@ -2789,6 +3384,12 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + [[package]] name = "rubato" version = "0.15.0" @@ -2807,6 +3408,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustfft" version = "6.2.0" @@ -2822,20 +3429,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "rustix" -version = "0.37.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustix" version = "0.38.32" @@ -2845,7 +3438,7 @@ dependencies = [ "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys", "windows-sys 0.52.0", ] @@ -2884,25 +3477,38 @@ checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" dependencies = [ "ab_glyph", "log", - "memmap2", - "smithay-client-toolkit", - "tiny-skia", + "memmap2 0.5.10", + "smithay-client-toolkit 0.16.1", + "tiny-skia 0.8.4", +] + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2 0.9.5", + "smithay-client-toolkit 0.19.2", + "tiny-skia 0.11.4", ] [[package]] name = "serde" -version = "1.0.197" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -2922,9 +3528,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", @@ -2933,9 +3539,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -2980,9 +3586,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -2993,6 +3599,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "simdeez" version = "2.0.0-dev3" @@ -3049,36 +3664,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" dependencies = [ "bitflags 1.3.2", - "calloop", + "calloop 0.10.6", "dlib", "lazy_static", "log", - "memmap2", + "memmap2 0.5.10", "nix 0.24.3", "pkg-config", - "wayland-client", - "wayland-cursor", - "wayland-protocols", + "wayland-client 0.29.5", + "wayland-cursor 0.29.5", + "wayland-protocols 0.29.5", ] [[package]] -name = "smithay-clipboard" -version = "0.6.6" +name = "smithay-client-toolkit" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "smithay-client-toolkit", - "wayland-client", + "bitflags 2.5.0", + "calloop 0.13.0", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2 0.9.5", + "rustix", + "thiserror", + "wayland-backend", + "wayland-client 0.31.6", + "wayland-csd-frame", + "wayland-cursor 0.31.6", + "wayland-protocols 0.32.4", + "wayland-protocols-wlr", + "wayland-scanner 0.31.5", + "xkeysym", ] [[package]] -name = "socket2" -version = "0.4.10" +name = "smithay-clipboard" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" dependencies = [ "libc", - "winapi", + "smithay-client-toolkit 0.19.2", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", ] [[package]] @@ -3136,16 +3776,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "strsim" -version = "0.11.1" +name = "svgtypes" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "ed4b0611e7f3277f68c0fa18e385d9e2d26923691379690039548f867cef02a7" +dependencies = [ + "kurbo", + "siphasher", +] [[package]] name = "svgtypes" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed4b0611e7f3277f68c0fa18e385d9e2d26923691379690039548f867cef02a7" +checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" dependencies = [ "kurbo", "siphasher", @@ -3306,6 +3950,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.19", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.10.1" @@ -3313,8 +3976,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand 2.0.2", - "rustix 0.38.32", + "fastrand", + "rustix", "windows-sys 0.52.0", ] @@ -3385,7 +4048,22 @@ dependencies = [ "bytemuck", "cfg-if", "png", - "tiny-skia-path", + "tiny-skia-path 0.8.4", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png", + "tiny-skia-path 0.11.4", ] [[package]] @@ -3399,6 +4077,17 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -3431,21 +4120,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.12" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.9", + "toml_edit 0.22.21", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -3456,7 +4145,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.5.0", "toml_datetime", "winnow 0.5.40", ] @@ -3467,22 +4156,22 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.5.0", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.9" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.5.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.5", + "winnow 0.6.18", ] [[package]] @@ -3542,11 +4231,20 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" name = "uds_windows" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "winapi", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ - "memoffset 0.9.1", - "tempfile", - "winapi", + "version_check", ] [[package]] @@ -3570,6 +4268,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "url" version = "2.5.0" @@ -3597,8 +4301,22 @@ dependencies = [ "base64", "log", "pico-args", - "usvg-parser", - "usvg-tree", + "usvg-parser 0.31.0", + "usvg-tree 0.31.0", + "xmlwriter", +] + +[[package]] +name = "usvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" +dependencies = [ + "base64", + "log", + "pico-args", + "usvg-parser 0.37.0", + "usvg-tree 0.37.0", "xmlwriter", ] @@ -3608,15 +4326,33 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2352a2c05655a7e4d3dca76cf65764efce35527472668bae5c6fc876b4c996d" dependencies = [ - "data-url", + "data-url 0.2.0", "flate2", - "imagesize", + "imagesize 0.11.0", "kurbo", "log", "rosvgtree", "strict-num", - "svgtypes", - "usvg-tree", + "svgtypes 0.11.0", + "usvg-tree 0.31.0", +] + +[[package]] +name = "usvg-parser" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" +dependencies = [ + "data-url 0.3.1", + "flate2", + "imagesize 0.12.0", + "kurbo", + "log", + "roxmltree 0.19.0", + "simplecss", + "siphasher", + "svgtypes 0.13.0", + "usvg-tree 0.37.0", ] [[package]] @@ -3628,14 +4364,31 @@ dependencies = [ "kurbo", "rctree", "strict-num", - "svgtypes", + "svgtypes 0.11.0", ] [[package]] -name = "utf8parse" -version = "0.2.1" +name = "usvg-tree" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" +dependencies = [ + "rctree", + "strict-num", + "svgtypes 0.13.0", + "tiny-skia-path 0.11.4", +] + +[[package]] +name = "v_frame" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] [[package]] name = "vec_map" @@ -3643,6 +4396,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.4" @@ -3651,18 +4410,17 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vk-parse" -version = "0.8.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6a0bda9bbe6b9e50e6456c80aa8fe4cca3b21e4311a1130c41e4915ec2e32a" +checksum = "81086c28be67a8759cd80cbb3c8f7b520e0874605fc5eb74d5a1c9c2d1878e79" dependencies = [ "xml-rs", ] [[package]] name = "vulkano" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1f15eeb9d93a05eb3c237332a10806eac1eb82444e54485bfcc1859c483c23" +version = "0.34.0" +source = "git+https://github.com/vulkano-rs/vulkano.git?rev=4a77d39#4a77d39b8562c9b8a337ee643c58f4f01ba6079b" dependencies = [ "ahash", "ash", @@ -3670,14 +4428,15 @@ dependencies = [ "core-graphics-types", "crossbeam-queue", "half", - "heck", - "indexmap 1.9.3", - "libloading 0.7.4", + "heck 0.4.1", + "indexmap 2.5.0", + "libloading 0.8.3", "objc", "once_cell", "parking_lot", "proc-macro2", "quote", + "raw-window-handle 0.6.2", "regex", "serde", "serde_json", @@ -3689,62 +4448,41 @@ dependencies = [ [[package]] name = "vulkano-macros" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "895b8a2cac1e7650d2d0552f2392da0970a358515ac11a34adaf19bfdc771b98" +version = "0.34.0" +source = "git+https://github.com/vulkano-rs/vulkano.git?rev=4a77d39#4a77d39b8562c9b8a337ee643c58f4f01ba6079b" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.58", ] [[package]] name = "vulkano-shaders" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f8cf18e9becbc6d39f1c39e26bcf69546c93989553eb5748cd734a8a697a6e5" +version = "0.34.0" +source = "git+https://github.com/vulkano-rs/vulkano.git?rev=4a77d39#4a77d39b8562c9b8a337ee643c58f4f01ba6079b" dependencies = [ "ahash", - "heck", + "heck 0.4.1", "proc-macro2", "quote", "shaderc", - "syn 1.0.109", - "vulkano", -] - -[[package]] -name = "vulkano-util" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a71b6df05a391161c1baec645a918437c2949d3494bf74c8358fde291d37f5f4" -dependencies = [ - "ahash", + "syn 2.0.58", "vulkano", - "vulkano-win", - "winit", ] [[package]] name = "vulkano-win" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666c77efe5ea82837781961a6bcd957ee2e926777e8de0005f580335d6eaefe7" +version = "0.34.0" +source = "git+https://github.com/vulkano-rs/vulkano.git?rev=4a77d39#4a77d39b8562c9b8a337ee643c58f4f01ba6079b" dependencies = [ "core-graphics-types", "objc", - "raw-window-handle", + "raw-window-handle 0.5.2", "vulkano", - "winit", + "winit 0.28.7", ] -[[package]] -name = "waker-fn" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" - [[package]] name = "walkdir" version = "2.5.0" @@ -3757,38 +4495,40 @@ dependencies = [ [[package]] name = "wasabi" -version = "0.1.4" +version = "1.0.0" dependencies = [ "atomic_float", "bytemuck", - "clap 4.5.4", "colors-transform", - "confy", "crossbeam-channel", - "directories 5.0.1", + "directories", "egui", "egui-winit", + "egui_extras", "egui_winit_vulkano", "enum_dispatch", "gen-iter", "ico", "kdmapi", "midi-toolkit-rs", - "num_enum 0.7.2", + "midir", + "num_enum 0.7.3", + "open", "palette", "rand", + "raw-window-handle 0.6.2", "rayon", - "resvg", + "resvg 0.31.1", "rfd", - "rustc-hash", + "rustc-hash 2.0.0", "serde", "serde_derive", - "toml 0.8.12", + "serde_json", + "toml 0.8.19", "vulkano", "vulkano-shaders", - "vulkano-util", "vulkano-win", - "winit", + "winit 0.30.5", "winres", "xsynth-core", "xsynth-realtime", @@ -3875,6 +4615,20 @@ dependencies = [ "riff", ] +[[package]] +name = "wayland-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys 0.31.5", +] + [[package]] name = "wayland-client" version = "0.29.5" @@ -3887,8 +4641,20 @@ dependencies = [ "nix 0.24.3", "scoped-tls", "wayland-commons", - "wayland-scanner", - "wayland-sys", + "wayland-scanner 0.29.5", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-client" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" +dependencies = [ + "bitflags 2.5.0", + "rustix", + "wayland-backend", + "wayland-scanner 0.31.5", ] [[package]] @@ -3900,7 +4666,18 @@ dependencies = [ "nix 0.24.3", "once_cell", "smallvec", - "wayland-sys", + "wayland-sys 0.29.5", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.5.0", + "cursor-icon", + "wayland-backend", ] [[package]] @@ -3910,7 +4687,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" dependencies = [ "nix 0.24.3", - "wayland-client", + "wayland-client 0.29.5", + "xcursor", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a94697e66e76c85923b0d28a0c251e8f0666f58fc47d316c0f4da6da75d37cb" +dependencies = [ + "rustix", + "wayland-client 0.31.6", "xcursor", ] @@ -3921,9 +4709,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" dependencies = [ "bitflags 1.3.2", - "wayland-client", + "wayland-client 0.29.5", "wayland-commons", - "wayland-scanner", + "wayland-scanner 0.29.5", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client 0.31.6", + "wayland-scanner 0.31.5", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0a41a6875e585172495f7a96dfa42ca7e0213868f4f15c313f7c33221a7eff" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client 0.31.6", + "wayland-protocols 0.32.4", + "wayland-scanner 0.31.5", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad87b5fd1b1d3ca2f792df8f686a2a11e3fe1077b71096f7a175ab699f89109" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client 0.31.6", + "wayland-protocols 0.32.4", + "wayland-scanner 0.31.5", ] [[package]] @@ -3937,6 +4763,17 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "wayland-scanner" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + [[package]] name = "wayland-sys" version = "0.29.5" @@ -3948,6 +4785,18 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "wayland-sys" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.69" @@ -3958,19 +4807,30 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webbrowser" -version = "0.8.13" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1b04c569c83a9bb971dd47ec6fd48753315f4bf989b9b04a2e7ca4d7f0dc950" +checksum = "425ba64c1e13b1c6e8c5d2541c8fac10022ca584f33da781db01b5756aef1f4e" dependencies = [ + "block2 0.5.1", "core-foundation", "home", "jni", "log", "ndk-context", - "objc", - "raw-window-handle", + "objc2 0.5.2", + "objc2-foundation", "url", "web-sys", ] @@ -4018,7 +4878,17 @@ version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" dependencies = [ - "windows-core", + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" +dependencies = [ + "windows-core 0.56.0", "windows-targets 0.52.6", ] @@ -4032,11 +4902,45 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "windows-interface" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "windows-result" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ "windows-targets 0.52.6", ] @@ -4261,35 +5165,87 @@ version = "0.28.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" dependencies = [ - "android-activity", + "android-activity 0.4.3", "bitflags 1.3.2", - "cfg_aliases", + "cfg_aliases 0.1.1", "core-foundation", - "core-graphics", + "core-graphics 0.22.3", "dispatch", "instant", "libc", "log", "mio", "ndk 0.7.0", - "objc2", + "objc2 0.3.0-beta.3.patch-leaks.3", "once_cell", "orbclient", "percent-encoding", - "raw-window-handle", + "raw-window-handle 0.5.2", "redox_syscall 0.3.5", - "sctk-adwaita", - "smithay-client-toolkit", + "sctk-adwaita 0.5.4", + "smithay-client-toolkit 0.16.1", "wasm-bindgen", - "wayland-client", + "wayland-client 0.29.5", "wayland-commons", - "wayland-protocols", - "wayland-scanner", + "wayland-protocols 0.29.5", + "wayland-scanner 0.29.5", "web-sys", "windows-sys 0.45.0", "x11-dl", ] +[[package]] +name = "winit" +version = "0.30.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" +dependencies = [ + "ahash", + "android-activity 0.6.0", + "atomic-waker", + "bitflags 2.5.0", + "block2 0.5.1", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation", + "core-graphics 0.23.2", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2 0.9.5", + "ndk 0.9.0", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle 0.6.2", + "redox_syscall 0.4.1", + "rustix", + "sctk-adwaita 0.10.1", + "smithay-client-toolkit 0.19.2", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client 0.31.6", + "wayland-protocols 0.32.4", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + [[package]] name = "winnow" version = "0.5.40" @@ -4301,9 +5257,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -4334,8 +5290,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" dependencies = [ + "as-raw-xcb-connection", "gethostname", - "rustix 0.38.32", + "libc", + "libloading 0.8.3", + "once_cell", + "rustix", "x11rb-protocol", ] @@ -4353,14 +5313,33 @@ checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" [[package]] name = "xdg-home" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e5a325c3cb8398ad6cf859c1135b25dd29e186679cf2da7581d9679f63b38e" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" dependencies = [ "libc", - "winapi", + "windows-sys 0.59.0", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.5.0", + "dlib", + "log", + "once_cell", + "xkeysym", ] +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + [[package]] name = "xml-rs" version = "0.8.20" @@ -4392,6 +5371,7 @@ dependencies = [ "lazy_static", "proc-macro2", "rayon", + "serde", "simdeez", "spin_sleep", "symphonia", @@ -4413,6 +5393,7 @@ dependencies = [ "crossbeam-channel", "lazy_static", "rayon", + "serde", "spin_sleep", "to_vec", "wav", @@ -4437,30 +5418,28 @@ dependencies = [ [[package]] name = "zbus" -version = "3.15.2" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +checksum = "c9ff46f2a25abd690ed072054733e0bc3157e3d4c45f41bd183dce09c2ff8ab9" dependencies = [ "async-broadcast", "async-executor", - "async-fs 1.6.0", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-fs", + "async-io", + "async-lock", "async-process", "async-recursion", "async-task", "async-trait", "blocking", - "byteorder", "derivative", "enumflags2", - "event-listener 2.5.3", + "event-listener", "futures-core", "futures-sink", "futures-util", "hex", - "nix 0.26.4", - "once_cell", + "nix 0.28.0", "ordered-stream", "rand", "serde", @@ -4469,7 +5448,7 @@ dependencies = [ "static_assertions", "tracing", "uds_windows", - "winapi", + "windows-sys 0.52.0", "xdg-home", "zbus_macros", "zbus_names", @@ -4478,11 +5457,11 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "3.15.2" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +checksum = "4e0e3852c93dcdb49c9462afe67a2a468f7bd464150d866e861eaf06208633e0" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "regex", @@ -4492,9 +5471,9 @@ dependencies = [ [[package]] name = "zbus_names" -version = "2.6.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", @@ -4521,6 +5500,12 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-inflate" version = "0.2.54" @@ -4530,15 +5515,23 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zune-jpeg" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" -version = "3.15.2" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +checksum = "2c1b3ca6db667bfada0f1ebfc94b2b1759ba25472ee5373d4551bb892616389a" dependencies = [ - "byteorder", + "endi", "enumflags2", - "libc", "serde", "static_assertions", "url", @@ -4547,11 +5540,11 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.15.2" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +checksum = "b7a4b236063316163b69039f77ce3117accb41a09567fd24c168e43491e521bc" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 1.0.109", @@ -4560,9 +5553,9 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +checksum = "00bedb16a193cc12451873fee2a1bc6550225acece0e36f333e68326c73c8172" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 1b5af1e..acb15b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,54 +1,56 @@ [package] name = "wasabi" -version = "0.1.4" +version = "1.0.0" edition = "2021" [dependencies] -egui_winit_vulkano = "0.24.0" -vulkano-shaders = "0.33.0" -vulkano-win = "0.33.0" -vulkano-util = "0.33.0" -egui-winit = "0.21.1" -bytemuck = "1.13.1" -vulkano = "0.33.0" -kdmapi = { git = "https://github.com/arduano/kdmapi.git", rev = "4116b00" } -egui = "0.21.0" -winit = "0.28.3" -rayon = "1.7.0" +egui = { version = "0.29", features = ["serde"] } +egui_extras = { version = "0.29", features = ["svg"] } +egui-winit = "0.29" +egui_winit_vulkano = { git = "https://github.com/MyBlackMIDIScore/egui_winit_vulkano.git", rev = "aa24f97" } +winit = { version = "0.30", default-features = false } +raw-window-handle = "0.6" +vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "4a77d39" } +vulkano-shaders = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "4a77d39" } +vulkano-win = { git = "https://github.com/vulkano-rs/vulkano.git", rev = "4a77d39" } midi-toolkit-rs = "0.1.0" -xsynth-core = "0.3.1" -xsynth-realtime = "0.3.1" +xsynth-core = { version = "0.3.1", features = ["serde"] } +xsynth-realtime = { version = "0.3.1", features = ["serde"] } +kdmapi-rs = { package = "kdmapi", git = "https://github.com/arduano/kdmapi.git", rev = "4116b00" } +serde = "1.0.210" +serde_derive = "1.0.210" +serde_json = "1.0" +toml = "0.8.19" +bytemuck = "1.18.0" +rayon = "1.10" +enum_dispatch = "0.3.13" gen-iter = { git = "https://github.com/arduano/gen-iter.git", rev = "64e28bc" } -enum_dispatch = "0.3.11" -palette = "0.7.1" -crossbeam-channel = "0.5.8" +crossbeam-channel = "0.5.13" +rustc-hash = "2.0.0" rand = "0.8.5" -confy = "0.5.1" -serde_derive = "1.0.160" -serde = "1.0.160" -toml = "0.8.0" -colors-transform = "0.2.11" -directories = "5.0.0" -rustc-hash = "1.1.0" -atomic_float = "0.1.0" -ico = { git = "https://github.com/StratusFearMe21/rust-ico", branch = "patch-1" } -clap = "4.2.4" -num_enum = "0.7.0" -rfd = { version = "0.12.0", default-features = false, features = [ - 'xdg-portal', -] } +directories = "5.0.1" +atomic_float = "1.1.0" +ico = "0.3.0" +rfd = "0.15.0" +open = "5.3.0" -[profile.dev] -opt-level = 2 +num_enum = "0.7.3" +palette = "0.7.6" +colors-transform = "0.2" +midir = "0.10.0" -[profile.release] -opt-level = 3 -codegen-units = 1 -lto = true [build-dependencies] resvg = { version = "0.31.0", default-features = false } -ico = { git = "https://github.com/StratusFearMe21/rust-ico", branch = "patch-1" } +ico = "0.3.0" [target.'cfg(windows)'.build-dependencies] winres = "0.1.12" + +[profile.dev] +opt-level = 2 + +[profile.release] +opt-level = 3 +codegen-units = 1 +lto = true diff --git a/assets/Poppins-Bold.ttf b/assets/Poppins-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..00559eeb290fb8036f10633ff0640447d827b27c GIT binary patch literal 153944 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%0AC7VmlS@LZ-TEZV}oirP&oT11@@uCp8_l( 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

dZ7LL{oK^sTLK}B|FDdi;JnIj;U0+siwNA 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 literal 0 HcmV?d00001 diff --git a/assets/folder.svg b/assets/folder.svg new file mode 100644 index 0000000..4bf65d8 --- /dev/null +++ b/assets/folder.svg @@ -0,0 +1,41 @@ + + + + + + + + diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 0000000..e4a6e4f --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,493 @@ + + + + diff --git a/assets/logo_16.svg b/assets/logo_16.svg new file mode 100644 index 0000000..47fcd9f --- /dev/null +++ b/assets/logo_16.svg @@ -0,0 +1,493 @@ + + + + diff --git a/assets/options.svg b/assets/options.svg new file mode 100644 index 0000000..2721548 --- /dev/null +++ b/assets/options.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + diff --git a/assets/pause.svg b/assets/pause.svg new file mode 100644 index 0000000..88b8442 --- /dev/null +++ b/assets/pause.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + diff --git a/assets/pin.svg b/assets/pin.svg new file mode 100644 index 0000000..d50933b --- /dev/null +++ b/assets/pin.svg @@ -0,0 +1,44 @@ + + + + + + + + + diff --git a/assets/play.svg b/assets/play.svg new file mode 100644 index 0000000..d253bd0 --- /dev/null +++ b/assets/play.svg @@ -0,0 +1,45 @@ + + + + + + + + + + diff --git a/assets/stop.svg b/assets/stop.svg new file mode 100644 index 0000000..bb2d1dc --- /dev/null +++ b/assets/stop.svg @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/src/audio_playback/empty.rs b/src/audio_playback/empty.rs new file mode 100644 index 0000000..add11c7 --- /dev/null +++ b/src/audio_playback/empty.rs @@ -0,0 +1,23 @@ +use super::*; + +pub struct EmptyPlayer {} + +impl EmptyPlayer { + pub fn new() -> Self { + Self {} + } +} + +impl MidiAudioPlayer for EmptyPlayer { + fn reset(&mut self) {} + + fn push_event(&mut self, _data: u32) {} + + fn voice_count(&self) -> u64 { + 0 + } + + fn configure(&mut self, _settings: &SynthSettings) {} + + fn set_soundfonts(&mut self, _soundfonts: &Vec) {} +} diff --git a/src/audio_playback/kdmapi.rs b/src/audio_playback/kdmapi.rs new file mode 100644 index 0000000..672407b --- /dev/null +++ b/src/audio_playback/kdmapi.rs @@ -0,0 +1,40 @@ +use super::*; +use kdmapi_rs::{KDMAPIStream, KDMAPI}; + +pub struct KdmapiPlayer { + stream: KDMAPIStream, + use_om_list: bool, +} + +impl KdmapiPlayer { + pub fn new() -> Self { + Self { + stream: KDMAPI.open_stream(), + use_om_list: false, + } + } +} + +impl MidiAudioPlayer for KdmapiPlayer { + fn reset(&mut self) { + self.stream.reset(); + } + + fn push_event(&mut self, data: u32) { + self.stream.send_direct_data(data); + } + + fn voice_count(&self) -> u64 { + 0 + } + + fn configure(&mut self, settings: &SynthSettings) { + self.use_om_list = settings.kdmapi.use_om_sflist; + } + + fn set_soundfonts(&mut self, _soundfonts: &Vec) { + if !self.use_om_list { + // TODO: Create OM compatible SF list to be sent through "LoadCustomSoundFontsList" + } + } +} diff --git a/src/audio_playback/midiout.rs b/src/audio_playback/midiout.rs new file mode 100644 index 0000000..2312390 --- /dev/null +++ b/src/audio_playback/midiout.rs @@ -0,0 +1,60 @@ +use std::thread; + +use super::*; +use crossbeam_channel::Sender; +use midir::MidiOutput; + +pub struct MidiDevicePlayer { + sender: Sender, +} + +impl MidiDevicePlayer { + pub fn new(device: String) -> Result { + let out = MidiOutput::new("wasabi").map_err(|e| format!("{:?}", e))?; + let ports = out.ports(); + if ports.is_empty() { + return Err("No MIDI devices available.".into()); + } + + let find = ports.iter().find(|d| { + if let Ok(name) = out.port_name(d) { + name == device + } else { + false + } + }); + let found = find.unwrap_or(&ports[0]); + let mut connection = out + .connect(found, "wasabi") + .map_err(|e| format!("{:?}", e))?; + + let (sender, receiver) = crossbeam_channel::bounded::(1000); + + thread::spawn(move || { + for data in receiver { + let message = data.to_le_bytes(); + connection.send(&message).unwrap_or_default(); + } + }); + + Ok(Self { sender }) + } +} + +impl MidiAudioPlayer for MidiDevicePlayer { + fn reset(&mut self) { + // TODO: With CC maybe? + } + + fn push_event(&mut self, data: u32) { + self.sender.send(data).unwrap(); + } + + fn voice_count(&self) -> u64 { + 0 + } + + fn configure(&mut self, _settings: &SynthSettings) {} + + fn set_soundfonts(&mut self, _soundfonts: &Vec) {} +} diff --git a/src/audio_playback/mod.rs b/src/audio_playback/mod.rs index dceb4c5..1d46af5 100644 --- a/src/audio_playback/mod.rs +++ b/src/audio_playback/mod.rs @@ -1,123 +1,54 @@ -use kdmapi::{KDMAPIStream, KDMAPI}; -use std::ops::RangeInclusive; -use xsynth_core::{channel::ChannelInitOptions, soundfont::SoundfontInitOptions}; -pub mod xsynth; - -#[derive(Clone)] -pub enum AudioPlayerType { - XSynth { - buffer: f64, - use_threadpool: bool, - ignore_range: RangeInclusive, - options: ChannelInitOptions, - }, - Kdmapi, +use crate::settings::{SynthSettings, WasabiSoundfont}; + +mod xsynth; +pub use xsynth::*; +mod kdmapi; +pub use kdmapi::*; +mod midiout; +pub use midiout::*; +mod empty; +pub use empty::*; + +pub trait MidiAudioPlayer: Send + Sync { + fn voice_count(&self) -> u64; + fn push_event(&mut self, data: u32); + fn configure(&mut self, settings: &SynthSettings); + fn set_soundfonts(&mut self, soundfonts: &Vec); + fn reset(&mut self); } -pub struct SimpleTemporaryPlayer { - player_type: AudioPlayerType, - xsynth: Option, - kdmapi: Option, +pub struct WasabiAudioPlayer { + player: Box, } -impl SimpleTemporaryPlayer { - pub fn new(player_type: AudioPlayerType) -> Self { - let (xsynth, kdmapi) = match player_type.clone() { - AudioPlayerType::XSynth { - buffer, - use_threadpool, - ignore_range, - options, - } => { - let xsynth = - xsynth::XSynthPlayer::new(buffer, use_threadpool, ignore_range, options); - (Some(xsynth), None) - } - AudioPlayerType::Kdmapi => { - let kdmapi = KDMAPI.open_stream(); - (None, Some(kdmapi)) - } - }; - Self { - player_type, - xsynth, - kdmapi, - } +impl WasabiAudioPlayer { + pub fn new(player: Box) -> Self { + Self { player: player } } - pub fn switch_player(&mut self, player_type: AudioPlayerType) { - self.reset(); - self.xsynth = None; - self.kdmapi = None; - let new_player = Self::new(player_type); - - self.player_type = new_player.player_type; - self.xsynth = new_player.xsynth; - self.kdmapi = new_player.kdmapi; + pub fn switch(&mut self, new_player: Box) { + self.player = new_player; } - pub fn get_voice_count(&self) -> u64 { - match self.player_type { - AudioPlayerType::XSynth { .. } => { - if let Some(xsynth) = &self.xsynth { - xsynth.get_voice_count() - } else { - 0 - } - } - AudioPlayerType::Kdmapi => 0, - } + pub fn voice_count(&self) -> u64 { + self.player.voice_count() } pub fn push_events(&mut self, data: impl Iterator) { - for e in data { - self.push_event(e); - } - } - - pub fn push_event(&mut self, data: u32) { - match self.player_type { - AudioPlayerType::XSynth { .. } => { - if let Some(xsynth) = self.xsynth.as_mut() { - xsynth.push_event(data); - } - } - AudioPlayerType::Kdmapi => { - if let Some(kdmapi) = self.kdmapi.as_mut() { - kdmapi.send_direct_data(data); - } - } + for ev in data { + self.player.push_event(ev); } } - pub fn reset(&mut self) { - match self.player_type { - AudioPlayerType::XSynth { .. } => { - if let Some(xsynth) = self.xsynth.as_mut() { - xsynth.reset(); - } - } - AudioPlayerType::Kdmapi => { - if let Some(kdmapi) = self.kdmapi.as_mut() { - kdmapi.reset(); - } - } - } + pub fn configure(&mut self, settings: &SynthSettings) { + self.player.configure(settings); } - pub fn set_layer_count(&mut self, layers: Option) { - if let AudioPlayerType::XSynth { .. } = self.player_type { - if let Some(xsynth) = self.xsynth.as_mut() { - xsynth.set_layer_count(layers); - } - } + pub fn set_soundfonts(&mut self, soundfonts: &Vec) { + self.player.set_soundfonts(soundfonts); } - pub fn set_soundfont(&mut self, path: &str, options: SoundfontInitOptions) { - if let AudioPlayerType::XSynth { .. } = self.player_type { - if let Some(xsynth) = self.xsynth.as_mut() { - xsynth.set_soundfont(path, options); - } - } + pub fn reset(&mut self) { + self.player.reset(); } } diff --git a/src/audio_playback/xsynth.rs b/src/audio_playback/xsynth.rs index fda330d..8e9e982 100644 --- a/src/audio_playback/xsynth.rs +++ b/src/audio_playback/xsynth.rs @@ -1,21 +1,21 @@ use std::{ - ops::{Deref, DerefMut, RangeInclusive}, - path::Path, + ops::{Deref, DerefMut}, sync::Arc, }; -use crate::WasabiSettings; +use crate::settings::WasabiSoundfont; use xsynth_core::{ - channel::{ChannelConfigEvent, ChannelEvent, ChannelInitOptions}, - soundfont::{EnvelopeOptions, SampleSoundfont, SoundfontBase, SoundfontInitOptions}, + channel::{ChannelConfigEvent, ChannelEvent}, + soundfont::{SampleSoundfont, SoundfontBase}, AudioStreamParams, }; use xsynth_realtime::{ - RealtimeEventSender, RealtimeSynth, RealtimeSynthStatsReader, SynthEvent, ThreadCount, - XSynthRealtimeConfig, + RealtimeEventSender, RealtimeSynth, RealtimeSynthStatsReader, SynthEvent, XSynthRealtimeConfig, }; +use super::*; + #[repr(transparent)] struct FuckYouImSend(T); @@ -38,30 +38,13 @@ impl DerefMut for FuckYouImSend { pub struct XSynthPlayer { sender: RealtimeEventSender, - pub stats: RealtimeSynthStatsReader, + stats: RealtimeSynthStatsReader, stream_params: AudioStreamParams, _synth: FuckYouImSend, } impl XSynthPlayer { - pub fn new( - buffer: f64, - use_threadpool: bool, - ignore_range: RangeInclusive, - options: ChannelInitOptions, - ) -> Self { - let config = XSynthRealtimeConfig { - render_window_ms: buffer, - channel_init_options: options, - ignore_range, - multithreading: if use_threadpool { - ThreadCount::Auto - } else { - ThreadCount::None - }, - ..Default::default() - }; - + pub fn new(config: XSynthRealtimeConfig) -> Self { let synth = FuckYouImSend(RealtimeSynth::open_with_default_output(config)); let sender = synth.get_senders(); let stream_params = synth.stream_params(); @@ -74,50 +57,51 @@ impl XSynthPlayer { _synth: synth, } } +} - pub fn get_voice_count(&self) -> u64 { +impl MidiAudioPlayer for XSynthPlayer { + fn voice_count(&self) -> u64 { self.stats.voice_count() } - pub fn push_event(&mut self, data: u32) { + fn push_event(&mut self, data: u32) { self.sender.send_event_u32(data); } - pub fn reset(&mut self) { + fn reset(&mut self) { self.sender.reset_synth(); } - pub fn set_layer_count(&mut self, layers: Option) { + fn configure(&mut self, settings: &SynthSettings) { + let layers = if settings.xsynth.limit_layers { + Some(settings.xsynth.layers) + } else { + None + }; + self.sender .send_event(SynthEvent::AllChannels(ChannelEvent::Config( ChannelConfigEvent::SetLayerCount(layers), ))); } - pub fn set_soundfont(&mut self, path: &str, options: SoundfontInitOptions) { - if !path.is_empty() && Path::new(path).exists() { - let samplesf = SampleSoundfont::new(path, self.stream_params, options); - if let Ok(sf) = samplesf { - let soundfont: Arc = Arc::new(sf); - self.sender - .send_event(SynthEvent::AllChannels(ChannelEvent::Config( - ChannelConfigEvent::SetSoundfonts(vec![soundfont]), - ))); + fn set_soundfonts(&mut self, soundfonts: &Vec) { + // TODO: Load in thread + let mut out: Vec> = Vec::new(); + + for sf in soundfonts { + if sf.enabled { + if let Ok(sf) = + SampleSoundfont::new(sf.path.clone(), self.stream_params, sf.options) + { + out.push(Arc::new(sf)); + } } } - } -} - -pub fn convert_to_sf_init(settings: &WasabiSettings) -> SoundfontInitOptions { - SoundfontInitOptions { - vol_envelope_options: EnvelopeOptions::default(), - use_effects: settings.synth.use_effects, - ..Default::default() - } -} -pub fn convert_to_channel_init(settings: &WasabiSettings) -> ChannelInitOptions { - ChannelInitOptions { - fade_out_killing: settings.synth.fade_out_kill, + self.sender + .send_event(SynthEvent::AllChannels(ChannelEvent::Config( + ChannelConfigEvent::SetSoundfonts(out), + ))); } } diff --git a/src/gui/window.rs b/src/gui/window.rs index fa6f0f9..3501d65 100644 --- a/src/gui/window.rs +++ b/src/gui/window.rs @@ -4,68 +4,57 @@ mod keyboard_layout; mod scene; mod stats; -mod settings_window; -mod top_panel; -mod xsynth_settings; - -use std::{ - path::PathBuf, - sync::{Arc, RwLock}, - time::Duration, -}; +mod about; +mod playback_panel; +mod settings; +mod shortcuts; + +use std::path::Path; +use std::sync::{Arc, RwLock}; +use std::thread; +use std::{path::PathBuf, time::Duration}; -use egui::{style::Margin, Frame, Visuals}; +use crossbeam_channel::{Receiver, Sender}; +use egui::FontFamily::{Monospace, Proportional}; +use egui::FontId; +use egui::Frame; +use settings::SettingsWindow; +use crate::audio_playback::{EmptyPlayer, MidiDevicePlayer}; use crate::{ - audio_playback::{ - xsynth::{convert_to_channel_init, convert_to_sf_init}, - AudioPlayerType, SimpleTemporaryPlayer, - }, + audio_playback::{KdmapiPlayer, MidiAudioPlayer, WasabiAudioPlayer, XSynthPlayer}, gui::window::{keyboard::GuiKeyboard, scene::GuiRenderScene}, midi::{CakeMIDIFile, InRamMIDIFile, LiveLoadMIDIFile, MIDIFileBase, MIDIFileUnion}, - settings::{MidiLoading, Synth, WasabiSettings}, + settings::{MidiParsing, Synth, WasabiSettings}, state::WasabiState, GuiRenderer, GuiState, }; +const WIN_MARGIN: egui::Margin = egui::Margin::same(12.0); +const SPACE: f32 = 12.0; + pub struct GuiWasabiWindow { render_scene: GuiRenderScene, keyboard_layout: keyboard_layout::KeyboardLayout, keyboard: GuiKeyboard, midi_file: Option, - synth: Arc>, + synth: Arc>, fps: fps::Fps, + + settings_win: SettingsWindow, + midi_picker: (Sender, Receiver), } impl GuiWasabiWindow { pub fn new(renderer: &mut GuiRenderer, settings: &mut WasabiSettings) -> GuiWasabiWindow { - let synth = match settings.synth.synth { - Synth::Kdmapi => Arc::new(RwLock::new(SimpleTemporaryPlayer::new( - AudioPlayerType::Kdmapi, - ))), - Synth::XSynth => { - let synth = Arc::new(RwLock::new(SimpleTemporaryPlayer::new( - AudioPlayerType::XSynth { - buffer: settings.synth.buffer_ms, - use_threadpool: settings.synth.use_threadpool, - ignore_range: settings.synth.vel_ignore.clone(), - options: convert_to_channel_init(settings), - }, - ))); - synth - .write() - .unwrap() - .set_soundfont(&settings.synth.sfz_path, convert_to_sf_init(settings)); - synth - .write() - .unwrap() - .set_layer_count(match settings.synth.layer_count { - 0 => None, - _ => Some(settings.synth.layer_count), - }); - synth - } - }; + let synth = Self::create_synth(settings); + let synth = Arc::new(RwLock::new(WasabiAudioPlayer::new(synth))); + + let mut settings_win = SettingsWindow::new(settings); + settings_win.load_palettes(); + settings_win.load_midi_devices(settings); + + let midi_picker = crossbeam_channel::unbounded(); GuiWasabiWindow { render_scene: GuiRenderScene::new(renderer), @@ -74,32 +63,143 @@ impl GuiWasabiWindow { midi_file: None, synth, fps: fps::Fps::new(), + + settings_win, + midi_picker, } } + #[inline(always)] + fn set_style(ctx: &egui::Context, _settings: &WasabiSettings) { + // Set theme + ctx.style_mut(|style| { + style.visuals.panel_fill = egui::Color32::from_rgb(18, 18, 18); + style.visuals.window_fill = style.visuals.panel_fill; + style.visuals.widgets.inactive.weak_bg_fill = style.visuals.panel_fill; + style.visuals.widgets.hovered.bg_stroke = egui::Stroke::NONE; + style.visuals.widgets.active.bg_stroke = egui::Stroke::NONE; + + style.visuals.selection.bg_fill = style.visuals.widgets.active.weak_bg_fill; + style.visuals.selection.stroke.color = egui::Color32::TRANSPARENT; + + style.visuals.override_text_color = Some(egui::Color32::from_rgb(210, 210, 210)); + + style.spacing.menu_margin = egui::Margin::same(8.0); + style.spacing.interact_size.y = 26.0; + }); + + // Set fonts + let mut fonts = egui::FontDefinitions::default(); + + fonts.font_data.insert( + "poppins".to_owned(), + egui::FontData::from_static(include_bytes!("../../assets/Poppins-Medium.ttf")), + ); + fonts + .families + .get_mut(&egui::FontFamily::Proportional) + .unwrap() + .insert(0, "poppins".to_owned()); + + fonts.font_data.insert( + "ubuntu".to_owned(), + egui::FontData::from_static(include_bytes!("../../assets/UbuntuSansMono-Medium.ttf")), + ); + fonts + .families + .get_mut(&egui::FontFamily::Monospace) + .unwrap() + .insert(0, "ubuntu".to_owned()); + + ctx.set_fonts(fonts); + + // Set font size + let mut style = (*ctx.style()).clone(); + style.text_styles = [ + (egui::TextStyle::Heading, FontId::new(26.0, Proportional)), + (egui::TextStyle::Body, FontId::new(16.0, Proportional)), + (egui::TextStyle::Monospace, FontId::new(12.0, Monospace)), + (egui::TextStyle::Button, FontId::new(16.0, Proportional)), + (egui::TextStyle::Small, FontId::new(12.0, Proportional)), + ( + egui::TextStyle::Name("monospace big".into()), + FontId::new(22.0, Monospace), + ), + ] + .into(); + ctx.set_style(style); + } + /// Defines the layout of our UI pub fn layout( &mut self, - state: &mut GuiState, + gui_state: &mut GuiState, settings: &mut WasabiSettings, - wasabi_state: &mut WasabiState, + state: &mut WasabiState, ) { - let ctx = state.renderer.gui.context(); - self.fps.update(); - ctx.set_visuals(Visuals::dark()); + let ctx = gui_state.renderer.gui.context(); - if wasabi_state.settings_visible { - settings_window::draw_settings(self, settings, wasabi_state, &ctx); + let fps_limit = match settings.gui.fps_limit { + 0 => None, + f => Some(f), + }; + self.fps.update(fps_limit); + Self::set_style(&ctx, settings); + + { + let recv = self.midi_picker.1.clone(); + if !recv.is_empty() { + for midi in recv { + state.last_location = midi.clone(); + self.load_midi(settings, midi); + break; + } + } } - if wasabi_state.xsynth_settings_visible { - xsynth_settings::draw_xsynth_settings(self, settings, wasabi_state, &ctx); + + // Other windows + if state.show_settings { + self.settings_win + .show(&ctx, settings, state, self.synth.clone()); } - let height_prev = ctx.available_rect().height(); - if settings.visual.show_top_pannel { - top_panel::draw_panel(self, settings, wasabi_state, &ctx); + if state.show_about { + self.show_about(&ctx, state); + } + + if state.show_shortcuts { + self.show_shortcuts(&ctx, state); } + // Set global keyboard shortcuts + ctx.input(|events| { + for event in &events.events { + if let egui::Event::Key { + key, + pressed, + modifiers, + .. + } = event + { + if *pressed && modifiers.ctrl { + match key { + egui::Key::F => state.panel_pinned = !state.panel_pinned, + egui::Key::G => state.stats_visible = !state.stats_visible, + egui::Key::O => self.open_midi_dialog(state), + _ => {} + } + } + if *pressed && modifiers.alt && key == &egui::Key::Enter { + state.fullscreen = !state.fullscreen + } + } + } + }); + + // Render the panel + let height_prev = ctx.available_rect().height(); + self.show_playback_panel(&ctx, state); + // Calculate available space left for keyboard and notes // We must render notes before keyboard because the notes // renderer tells us the key colors @@ -107,21 +207,21 @@ impl GuiWasabiWindow { let height = available.height(); let panel_height = height_prev - height; let keyboard_height = - (11.6 / settings.midi.key_range.len() as f32 * available.width()).min(height / 2.0); + (11.6 / settings.scene.key_range.len() as f32 * available.width()).min(height / 2.0); let notes_height = height - keyboard_height; let key_view = self.keyboard_layout.get_view_for_keys( - *settings.midi.key_range.start() as usize, - *settings.midi.key_range.end() as usize, + *settings.scene.key_range.start() as usize, + *settings.scene.key_range.end() as usize, ); let no_frame = Frame::default() - .inner_margin(Margin::same(0.0)) - .fill(settings.visual.bg_color); + .inner_margin(egui::Margin::same(0.0)) + .fill(settings.scene.bg_color); let mut stats = stats::GuiMidiStats::empty(); - let mut render_result_data = None; + let mut render_result_data: Option = None; // Render the notes egui::TopBottomPanel::top("Note panel") @@ -130,31 +230,45 @@ impl GuiWasabiWindow { .show_separator_line(false) .show(&ctx, |ui| { if let Some(midi_file) = self.midi_file.as_mut() { - let one_sec = Duration::from_secs(1); + let skip_dur = Duration::from_secs_f64(settings.gui.skip_control); let time = midi_file.timer().get_time(); + // Set playback keyboard shortcuts ui.input(|events| { for event in &events.events { - if let egui::Event::Key { key, pressed, .. } = event { + if let egui::Event::Key { + key, + pressed, + modifiers, + .. + } = event + { if pressed == &true { match key { egui::Key::ArrowRight => { - midi_file.timer_mut().seek(time + one_sec) + midi_file.timer_mut().seek(time + skip_dur) } egui::Key::ArrowLeft => { if midi_file.allows_seeking_backward() { - midi_file.timer_mut().seek(if time <= one_sec { + midi_file.timer_mut().seek(if time <= skip_dur { + // FIXME: Start deplay Duration::from_secs(0) } else { - time - one_sec + time - skip_dur }) } } egui::Key::ArrowUp => { - settings.midi.note_speed += 0.05; + if modifiers.ctrl { + settings.scene.note_speed += + settings.gui.speed_control; + } } egui::Key::ArrowDown => { - settings.midi.note_speed -= 0.05; + if modifiers.ctrl { + settings.scene.note_speed -= + settings.gui.speed_control; + } } egui::Key::Space => midi_file.timer_mut().toggle_pause(), _ => {} @@ -165,11 +279,11 @@ impl GuiWasabiWindow { }); let result = self.render_scene.draw( - state, + gui_state, ui, &key_view, midi_file, - settings.midi.note_speed, + settings.scene.note_speed, ); stats.set_rendered_note_count(result.notes_rendered); render_result_data = Some(result); @@ -182,36 +296,6 @@ impl GuiWasabiWindow { .frame(no_frame) .show_separator_line(false) .show(&ctx, |ui| { - ui.input(|events| { - for event in &events.events { - if let egui::Event::Key { - key, - pressed, - modifiers, - .. - } = event - { - if *pressed && modifiers.ctrl { - match key { - egui::Key::F => { - settings.visual.show_top_pannel = - !settings.visual.show_top_pannel - } - egui::Key::G => { - settings.visual.show_statistics = - !settings.visual.show_statistics - } - //egui::Key::O => self.open_midi_dialog(wasabi_state), - _ => {} - } - } - if *pressed && modifiers.alt && key == &egui::Key::Enter { - wasabi_state.fullscreen = !wasabi_state.fullscreen - } - } - } - }); - let colors = if let Some(data) = render_result_data { data.key_colors } else { @@ -219,33 +303,42 @@ impl GuiWasabiWindow { }; self.keyboard - .draw(ui, &key_view, &colors, &settings.visual.bar_color); + .draw(ui, &key_view, &colors, &settings.scene.bar_color); }); // Render the stats - if settings.visual.show_statistics { - let voice_count = self.synth.read().unwrap().get_voice_count(); + if state.stats_visible { + let voice_count = self.synth.read().unwrap().voice_count(); stats.set_voice_count(voice_count); - let pos = egui::Pos2::new(10.0, panel_height + 10.0); - stats::draw_stats(self, &ctx, pos, stats); + let pad = if settings.scene.statistics.floating { + 12.0 + } else { + 0.0 + }; + let pos = egui::Pos2::new(pad, panel_height + pad); + stats::draw_stats(self, &ctx, pos, stats, settings); } } - #[allow(unused_variables)] - pub fn open_midi_dialog(&mut self, settings: &mut WasabiSettings, state: &mut WasabiState) { - // If windows, just use the native dialog - let midi_path = rfd::FileDialog::new() - .add_filter("mid", &["mid"]) - .pick_file(); + pub fn open_midi_dialog(&mut self, state: &mut WasabiState) { + let sender = self.midi_picker.0.clone(); + let last_location = state.last_location.clone(); - if let Some(midi_path) = midi_path { - state.last_midi_file = Some(midi_path.clone()); - self.load_midi(settings, midi_path); - } + thread::spawn(move || { + let midi_path = rfd::FileDialog::new() + .add_filter("mid", &["mid", "MID"]) + .set_directory(last_location.parent().unwrap_or(Path::new("./"))) + .pick_file(); + + if let Some(midi_path) = midi_path { + sender.send(midi_path).unwrap_or_default(); + } + }); } pub fn load_midi(&mut self, settings: &mut WasabiSettings, midi_path: PathBuf) { + // TODO: Load in thread if let Some(midi_file) = self.midi_file.as_mut() { midi_file.timer_mut().pause(); } @@ -253,30 +346,30 @@ impl GuiWasabiWindow { self.midi_file = None; if let Some(midi_path) = midi_path.to_str() { - match settings.midi.midi_loading { - MidiLoading::Ram => { + match settings.midi.parsing { + MidiParsing::Ram => { let mut midi_file = MIDIFileUnion::InRam(InRamMIDIFile::load_from_file( midi_path, self.synth.clone(), - settings.midi.random_colors, + settings, )); midi_file.timer_mut().play(); self.midi_file = Some(midi_file); } - MidiLoading::Live => { + MidiParsing::Live => { let mut midi_file = MIDIFileUnion::Live(LiveLoadMIDIFile::load_from_file( midi_path, self.synth.clone(), - settings.midi.random_colors, + settings, )); midi_file.timer_mut().play(); self.midi_file = Some(midi_file); } - MidiLoading::Cake => { + MidiParsing::Cake => { let mut midi_file = MIDIFileUnion::Cake(CakeMIDIFile::load_from_file( midi_path, self.synth.clone(), - settings.midi.random_colors, + settings, )); midi_file.timer_mut().play(); self.midi_file = Some(midi_file); @@ -284,4 +377,23 @@ impl GuiWasabiWindow { } } } + + pub fn create_synth(settings: &WasabiSettings) -> Box { + let mut synth: Box = match settings.synth.synth { + Synth::XSynth => Box::new(XSynthPlayer::new(settings.synth.xsynth.config.clone())), + Synth::Kdmapi => Box::new(KdmapiPlayer::new()), + Synth::MidiDevice => { + if let Ok(midiout) = MidiDevicePlayer::new(settings.synth.midi_device.clone()) { + Box::new(midiout) + } else { + Box::new(EmptyPlayer::new()) + } + } + Synth::None => Box::new(EmptyPlayer::new()), + }; + synth.set_soundfonts(&settings.synth.soundfonts); + synth.configure(&settings.synth); + + synth + } } diff --git a/src/gui/window/about.rs b/src/gui/window/about.rs new file mode 100644 index 0000000..0f724cb --- /dev/null +++ b/src/gui/window/about.rs @@ -0,0 +1,152 @@ +use crate::state::WasabiState; +use std::env::consts::{ARCH, OS}; + +use super::GuiWasabiWindow; + +impl GuiWasabiWindow { + pub fn show_about(&mut self, ctx: &egui::Context, state: &mut WasabiState) { + let frame = + egui::Frame::inner_margin(egui::Frame::window(ctx.style().as_ref()), super::WIN_MARGIN); + let size = [600.0, 450.0]; + + egui::Window::new("About Wasabi") + .resizable(true) + .collapsible(false) + .title_bar(true) + .scroll([false, true]) + .enabled(true) + .frame(frame) + .fixed_size(size) + .open(&mut state.show_about) + .show(ctx, |ui| { + ui.add_space(4.0); + ui.horizontal(|ui| { + let image_size = 100.0; + + let title_size = 46.0; + let titleid = egui::FontId { + size: title_size, + ..Default::default() + }; + + let title_text = format!("Wasabi"); + let title_galley = ui.painter().layout_no_wrap( + title_text.to_owned(), + titleid, + egui::Color32::WHITE, + ); + + let logo_width = + image_size + ui.spacing().item_spacing.x + title_galley.size().x; + let space = ui.available_width() / 2.0 - (logo_width + 4.0) / 2.0; + + ui.add_space(space); + ui.add( + egui::Image::new(egui::include_image!("../../../assets/logo.svg")) + .fit_to_exact_size(egui::Vec2::new(image_size, image_size)), + ); + ui.add_space(4.0); + + ui.vertical(|ui| { + let text_height = title_galley.size().y; + let space = (image_size - text_height) / 2.0; + ui.add_space(space); + + ui.label(egui::RichText::new(title_text).size(title_size)); + }) + }); + + ui.add_space(10.0); + ui.separator(); + ui.add_space(10.0); + + let col_width = size[0] / 2.0; + ui.heading("Build Information"); + egui::Grid::new("buildinfo_grid") + .num_columns(2) + .min_col_width(col_width) + .striped(true) + .show(ui, |ui| { + ui.label("Version:"); + ui.label(env!("CARGO_PKG_VERSION")); + ui.end_row(); + + ui.label("Operating System:"); + ui.label(OS.to_string()); + ui.end_row(); + + ui.label("Architecture:"); + ui.label(ARCH.to_string()); + ui.end_row(); + }); + + ui.add_space(20.0); + + ui.heading("Libraries"); + egui::Grid::new("libraries_grid") + .num_columns(2) + .min_col_width(col_width) + .striped(true) + .show(ui, |ui| { + ui.label("Egui Version:"); + ui.label("0.29"); + ui.end_row(); + + ui.label("XSynth Version:"); + ui.label("0.3.1"); + ui.end_row(); + + ui.label("MIDI Toolkit Version:"); + ui.label("0.1.0"); + ui.end_row(); + }); + + let gh_text = "\u{1F310} GitHub"; + let gh_galley = ui.painter().layout_no_wrap( + gh_text.to_owned(), + ctx.style() + .text_styles + .iter() + .find(|v| v.0 == &egui::TextStyle::Button) + .unwrap() + .1 + .clone(), + egui::Color32::WHITE, + ); + + let upd_text = "\u{1F310} Check for updates"; + let upd_galley = ui.painter().layout_no_wrap( + upd_text.to_owned(), + ctx.style() + .text_styles + .iter() + .find(|v| v.0 == &egui::TextStyle::Button) + .unwrap() + .1 + .clone(), + egui::Color32::WHITE, + ); + + let mut h = ui.available_height(); + + let button_height = ui.spacing().button_padding.y * 2.0 + gh_galley.size().y; + h -= button_height; + ui.add_space(h); + + ui.horizontal(|ui| { + let w = ui.available_width(); + + let button_width = gh_galley.size().x + + upd_galley.size().x + + ui.spacing().button_padding.x * 4.0; + let w = w / 2.0 - button_width / 2.0; + ui.add_space(w); + + if ui.button(gh_text).clicked() { + open::that("https://github.com/BlackMIDIDevs/wasabi").unwrap(); + } + if ui.button(upd_text).clicked() {} + }); + }); + } +} diff --git a/src/gui/window/fps.rs b/src/gui/window/fps.rs index 380db49..8b19a5a 100644 --- a/src/gui/window/fps.rs +++ b/src/gui/window/fps.rs @@ -9,9 +9,10 @@ impl Fps { Self(VecDeque::new()) } - pub fn update(&mut self) { + pub fn update(&mut self, _limit: Option) { self.0.push_back(Instant::now()); while let Some(front) = self.0.front() { + // TODO: FPS limit if front.elapsed().as_secs_f64() > FPS_WINDOW { self.0.pop_front(); } else { diff --git a/src/gui/window/playback_panel.rs b/src/gui/window/playback_panel.rs new file mode 100644 index 0000000..53741ce --- /dev/null +++ b/src/gui/window/playback_panel.rs @@ -0,0 +1,220 @@ +use std::time::Duration; + +use egui::{popup_below_widget, PopupCloseBehavior}; + +use super::{GuiWasabiWindow, SPACE, WIN_MARGIN}; +use crate::{midi::MIDIFileBase, state::WasabiState, utils::convert_seconds_to_time_string}; + +impl GuiWasabiWindow { + pub fn show_playback_panel(&mut self, ctx: &egui::Context, state: &mut WasabiState) { + let mut mouse_over_panel = false; + if let Some(mouse) = ctx.pointer_latest_pos() { + if mouse.y < 60.0 { + mouse_over_panel = true; + } + } + let button_size = egui::Vec2::new(26.0, 26.0); + let icon_color = ctx.style().visuals.strong_text_color(); + let button_rounding = 8.0; + + let is_popup_open = ctx.memory(|mem| mem.is_popup_open(state.panel_popup_id)); + + let frame = egui::Frame::side_top_panel(&ctx.style()).inner_margin(WIN_MARGIN); + egui::TopBottomPanel::top("panel") + .frame(frame) + .show_separator_line(false) + .show_animated( + ctx, + state.panel_pinned || mouse_over_panel || is_popup_open, + |ui| { + ui.horizontal(|ui| { + let folder_img = + egui::Image::new(egui::include_image!("../../../assets/folder.svg")) + .fit_to_exact_size(button_size) + .tint(icon_color) + .rounding(button_rounding); + + if ui + .add(egui::ImageButton::new(folder_img)) + .on_hover_text("Open MIDI") + .clicked() + { + self.open_midi_dialog(state); + } + + let stop_img = + egui::Image::new(egui::include_image!("../../../assets/stop.svg")) + .fit_to_exact_size(button_size) + .tint(icon_color) + .rounding(button_rounding); + + if ui + .add(egui::ImageButton::new(stop_img)) + .on_hover_text("Unload") + .clicked() + { + if let Some(midi) = self.midi_file.take().as_mut() { + midi.timer_mut().pause(); + self.synth.write().unwrap().reset(); + } + } + + let playing = if let Some(midi) = self.midi_file.as_ref() { + !midi.timer().is_paused() + } else { + false + }; + + // FIXME + if playing { + let pause_img = + egui::Image::new(egui::include_image!("../../../assets/pause.svg")) + .fit_to_exact_size(button_size) + .tint(icon_color) + .rounding(button_rounding); + + if ui + .add(egui::ImageButton::new(pause_img)) + .on_hover_text("Pause") + .clicked() + { + if let Some(midi_file) = self.midi_file.as_mut() { + midi_file.timer_mut().pause(); + } + } + } else { + let play_img = + egui::Image::new(egui::include_image!("../../../assets/play.svg")) + .fit_to_exact_size(button_size) + .tint(icon_color) + .rounding(button_rounding); + + if ui + .add(egui::ImageButton::new(play_img)) + .on_hover_text("Play") + .clicked() + { + if let Some(midi_file) = self.midi_file.as_mut() { + midi_file.timer_mut().play(); + } + } + } + + ui.add_space(SPACE); + ui.separator(); + ui.add_space(SPACE); + + let mut time_passed = 0.0; + let mut time_total = 0.0; + + if let Some(midi) = self.midi_file.as_ref() { + time_total = midi.midi_length().unwrap_or(0.0); + time_passed = midi.timer().get_time().as_secs_f64(); + } + + let mut timeid = ui + .style() + .text_styles + .get(&egui::TextStyle::Monospace) + .unwrap() + .clone(); + timeid.size = 16.0; + let time_text = convert_seconds_to_time_string(time_passed); + let time_galley = ui.painter().layout_no_wrap( + time_text.clone(), + timeid.clone(), + egui::Color32::WHITE, + ); + + let length_text = convert_seconds_to_time_string(time_total); + let length_galley = ui.painter().layout_no_wrap( + length_text.clone(), + timeid.clone(), + egui::Color32::WHITE, + ); + + ui.spacing_mut().slider_width = ui.available_width() + - time_galley.size().x + - length_galley.size().x + - ui.spacing().item_spacing.x * 8.0 + - button_size.x * 2.0 + - ui.spacing().button_padding.x * 2.0 + - SPACE; + + ui.label(egui::RichText::new(time_text).font(timeid.clone())); + let mut empty_slider = + || ui.add(egui::Slider::new(&mut 0.0, 0.0..=1.0).show_value(false)); + if let Some(midi_file) = self.midi_file.as_mut() { + if let Some(length) = midi_file.midi_length() { + let mut time = midi_file.timer().get_time().as_secs_f64(); + let time_prev = time; + + ui.add( + egui::Slider::new(&mut time, 0.0..=length).show_value(false), + ); + if (time_prev != time) + && (midi_file.allows_seeking_backward() || time_prev < time) + { + midi_file.timer_mut().seek(Duration::from_secs_f64(time)); + } + } else { + empty_slider(); + } + } else { + empty_slider(); + } + ui.label(egui::RichText::new(length_text).font(timeid.clone())); + + ui.add_space(SPACE); + ui.separator(); + ui.add_space(SPACE); + + let options_img = + egui::Image::new(egui::include_image!("../../../assets/options.svg")) + .fit_to_exact_size(button_size) + .tint(icon_color) + .rounding(button_rounding); + + let options = ui.add(egui::ImageButton::new(options_img)); + + if options.clicked() { + ui.memory_mut(|mem| mem.toggle_popup(state.panel_popup_id)); + } + popup_below_widget( + ui, + state.panel_popup_id, + &options, + PopupCloseBehavior::CloseOnClick, + |ui| { + ui.set_min_width(130.0); + + if ui.button("Settings").clicked() { + state.show_settings = true; + } + if ui.button("Shortcuts").clicked() { + state.show_shortcuts = true; + } + if ui.button("About").clicked() { + state.show_about = true; + } + }, + ); + + let arrow_img = + egui::Image::new(egui::include_image!("../../../assets/pin.svg")) + .fit_to_exact_size(button_size) + .tint(icon_color) + .rounding(button_rounding); + + if ui + .add(egui::ImageButton::new(arrow_img).selected(state.panel_pinned)) + .on_hover_text("Pin Panel") + .clicked() + { + state.panel_pinned = !state.panel_pinned; + } + }); + }, + ); + } +} diff --git a/src/gui/window/scene.rs b/src/gui/window/scene.rs index c78fb28..9a35c4a 100644 --- a/src/gui/window/scene.rs +++ b/src/gui/window/scene.rs @@ -1,7 +1,7 @@ mod cake_system; mod note_list_system; -use egui::Ui; +use egui::{Image, Ui}; use crate::{ midi::{MIDIColor, MIDIFileUnion}, @@ -97,7 +97,8 @@ impl GuiRenderScene { .draw(key_view, frame, file, view_range), }; - ui.image(scene_image.id, [size[0] as f32, size[1] as f32]); + let img = Image::new((scene_image.id, [size[0] as f32, size[1] as f32].into())); + ui.add(img); result } diff --git a/src/gui/window/scene/cake_system/mod.rs b/src/gui/window/scene/cake_system/mod.rs index b49b735..b0c161b 100644 --- a/src/gui/window/scene/cake_system/mod.rs +++ b/src/gui/window/scene/cake_system/mod.rs @@ -5,23 +5,29 @@ use vulkano::{ buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, - RenderPassBeginInfo, SubpassContents, + RenderPassBeginInfo, SubpassBeginInfo, SubpassContents, }, descriptor_set::{ allocator::StandardDescriptorSetAllocator, PersistentDescriptorSet, WriteDescriptorSet, }, device::{Device, Queue}, format::Format, - image::{view::ImageView, AttachmentImage, ImageAccess, ImageViewAbstract}, - memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator}, + image::{view::ImageView, Image, ImageCreateInfo, ImageUsage}, + memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, pipeline::{ graphics::{ + color_blend::{ColorBlendAttachmentState, ColorBlendState}, depth_stencil::DepthStencilState, input_assembly::{InputAssemblyState, PrimitiveTopology}, - vertex_input::Vertex, + multisample::MultisampleState, + rasterization::RasterizationState, + vertex_input::{Vertex, VertexDefinition}, viewport::{Viewport, ViewportState}, + GraphicsPipelineCreateInfo, }, - GraphicsPipeline, Pipeline, PipelineBindPoint, + layout::PipelineDescriptorSetLayoutCreateInfo, + GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout, + PipelineShaderStageCreateInfo, }, render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, sync::{self, GpuFuture}, @@ -73,18 +79,18 @@ impl BufferSet { fn add_buffer( &mut self, - allocator: &StandardMemoryAllocator, + allocator: Arc, block: &CakeBlock, _key: &KeyPosition, ) { let data = Buffer::from_iter( - allocator, + allocator.clone(), BufferCreateInfo { usage: BufferUsage::STORAGE_BUFFER, ..Default::default() }, AllocationCreateInfo { - usage: MemoryUsage::Upload, + memory_type_filter: MemoryTypeFilter::PREFER_HOST, ..Default::default() }, block.tree.iter().copied(), @@ -110,8 +116,8 @@ pub struct CakeRenderer { buffers: BufferSet, pipeline_clear: Arc, render_pass_clear: Arc, - allocator: StandardMemoryAllocator, - depth_buffer: Arc>, + allocator: Arc, + depth_buffer: Arc, cb_allocator: StandardCommandBufferAllocator, sd_allocator: StandardDescriptorSetAllocator, buffers_init: Subbuffer<[CakeNoteColumn]>, @@ -120,23 +126,25 @@ pub struct CakeRenderer { impl CakeRenderer { pub fn new(renderer: &GuiRenderer) -> CakeRenderer { - let allocator = StandardMemoryAllocator::new_default(renderer.device.clone()); + let allocator = Arc::new(StandardMemoryAllocator::new_default( + renderer.device.clone(), + )); let gfx_queue = renderer.queue.clone(); let render_pass_clear = vulkano::ordered_passes_renderpass!(gfx_queue.device().clone(), attachments: { final_color: { - load: Clear, - store: Store, format: renderer.format, samples: 1, + load_op: Clear, + store_op: Store, }, depth: { - load: Clear, - store: Store, format: Format::D16_UNORM, samples: 1, + load_op: Clear, + store_op: Store, } }, passes: [ @@ -150,38 +158,93 @@ impl CakeRenderer { .unwrap(); let depth_buffer = ImageView::new_default( - AttachmentImage::transient_input_attachment(&allocator, [1, 1], Format::D16_UNORM) - .unwrap(), + Image::new( + allocator.clone(), + ImageCreateInfo { + extent: [1, 1, 1], + format: Format::D16_UNORM, + usage: ImageUsage::SAMPLED, + ..Default::default() + }, + Default::default(), + ) + .unwrap(), ) .unwrap(); - let vs = vs::load(gfx_queue.device().clone()).expect("failed to create shader module"); - let fs = fs::load(gfx_queue.device().clone()).expect("failed to create shader module"); - let gs = gs::load(gfx_queue.device().clone()).expect("failed to create shader module"); - - let pipeline_base = GraphicsPipeline::start() - .input_assembly_state(InputAssemblyState::new().topology(PrimitiveTopology::PointList)) - .vertex_input_state(CakeNoteColumn::per_vertex()) - .vertex_shader(vs.entry_point("main").unwrap(), ()) - .fragment_shader(fs.entry_point("main").unwrap(), ()) - .geometry_shader(gs.entry_point("main").unwrap(), ()) - .viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant()) - .depth_stencil_state(DepthStencilState::simple_depth_test()); - - let pipeline_clear = pipeline_base - .clone() - .render_pass(Subpass::from(render_pass_clear.clone(), 0).unwrap()) - .build(gfx_queue.device().clone()) + let vs = vs::load(gfx_queue.device().clone()) + .expect("failed to create shader module") + .entry_point("main") + .unwrap(); + let fs = fs::load(gfx_queue.device().clone()) + .expect("failed to create shader module") + .entry_point("main") + .unwrap(); + let gs = gs::load(gfx_queue.device().clone()) + .expect("failed to create shader module") + .entry_point("main") .unwrap(); + let vertex_input_state = CakeNoteColumn::per_vertex() + .definition(&vs.info().input_interface) + .unwrap(); + let stages = [ + PipelineShaderStageCreateInfo::new(vs), + PipelineShaderStageCreateInfo::new(fs), + PipelineShaderStageCreateInfo::new(gs), + ]; + let layout = PipelineLayout::new( + renderer.device.clone(), + PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) + .into_pipeline_layout_create_info(renderer.device.clone()) + .unwrap(), + ) + .unwrap(); + let subpass = Subpass::from(render_pass_clear.clone(), 0).unwrap(); + + let pipeline_clear = GraphicsPipeline::new( + renderer.device.clone(), + None, + GraphicsPipelineCreateInfo { + stages: stages.into_iter().collect(), + vertex_input_state: Some(vertex_input_state), + input_assembly_state: Some(InputAssemblyState { + topology: PrimitiveTopology::PointList, + ..Default::default() + }), + viewport_state: Some(ViewportState { + viewports: [Viewport { + offset: [0.0, 0.0], + extent: [1280.0, 720.0], + depth_range: 0.0..=1.0, + }] + .into_iter() + .collect(), + ..Default::default() + }), + rasterization_state: Some(RasterizationState::default()), + multisample_state: Some(MultisampleState::default()), + color_blend_state: Some(ColorBlendState::with_attachment_states( + subpass.num_color_attachments(), + ColorBlendAttachmentState::default(), + )), + depth_stencil_state: Some(DepthStencilState::default()), + subpass: Some(subpass.into()), + ..GraphicsPipelineCreateInfo::layout(layout) + }, + ) + .unwrap(); + let buffers = Buffer::new_slice( - &allocator, + allocator.clone(), BufferCreateInfo { - usage: BufferUsage::VERTEX_BUFFER, + usage: BufferUsage::STORAGE_BUFFER + | BufferUsage::TRANSFER_DST + | BufferUsage::VERTEX_BUFFER, ..Default::default() }, AllocationCreateInfo { - usage: MemoryUsage::Upload, + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE, ..Default::default() }, BUFFER_ARRAY_LEN, @@ -199,7 +262,10 @@ impl CakeRenderer { renderer.device.clone(), Default::default(), ), - sd_allocator: StandardDescriptorSetAllocator::new(renderer.device.clone()), + sd_allocator: StandardDescriptorSetAllocator::new( + renderer.device.clone(), + Default::default(), + ), buffers_init: buffers, current_file_signature: None, } @@ -208,19 +274,19 @@ impl CakeRenderer { pub fn draw( &mut self, key_view: &KeyboardView, - final_image: Arc, + final_image: Arc, midi_file: &mut CakeMIDIFile, view_range: f64, ) -> RenderResultData { - let img_dims = final_image.image().dimensions().width_height(); - if self.depth_buffer.image().dimensions().width_height() != img_dims { + let img_dims = final_image.image().extent(); + if self.depth_buffer.image().extent() != img_dims { + let mut create_info: ImageCreateInfo = Default::default(); + create_info.extent = [img_dims[0], img_dims[1], 1]; + create_info.format = Format::D16_UNORM; + create_info.usage = ImageUsage::SAMPLED; + self.depth_buffer = ImageView::new_default( - AttachmentImage::transient_input_attachment( - &self.allocator, - img_dims, - Format::D16_UNORM, - ) - .unwrap(), + Image::new(self.allocator.clone(), create_info, Default::default()).unwrap(), ) .unwrap(); } @@ -231,7 +297,7 @@ impl CakeRenderer { self.buffers.clear(); for (i, block) in midi_file.key_blocks().iter().enumerate() { let key = key_view.key(i); - self.buffers.add_buffer(&self.allocator, block, &key); + self.buffers.add_buffer(self.allocator.clone(), block, &key); } } @@ -247,7 +313,7 @@ impl CakeRenderer { }; let border_width = crate::utils::calculate_border_width( - final_image.image().dimensions().width() as f32, + final_image.image().extent()[0] as f32, key_view.visible_range.len() as f32, ) as i32; @@ -318,41 +384,53 @@ impl CakeRenderer { 0, self.buffers.buffers.iter().map(|b| b.data.clone()), )], + [], ) .unwrap(); + let mut subpassbegininfo = SubpassBeginInfo::default(); + subpassbegininfo.contents = SubpassContents::Inline; + command_buffer_builder .begin_render_pass( RenderPassBeginInfo { clear_values: clears, ..RenderPassBeginInfo::framebuffer(framebuffer) }, - SubpassContents::Inline, + subpassbegininfo, ) .unwrap(); command_buffer_builder .bind_pipeline_graphics(pipeline.clone()) + .unwrap() .set_viewport( 0, - [Viewport { - origin: [0.0, 0.0], - dimensions: [img_dims[0] as f32, img_dims[1] as f32], - depth_range: 0.0..1.0, - }], + vec![Viewport { + offset: [0.0, 0.0], + extent: [img_dims[0] as f32, img_dims[1] as f32], + depth_range: 0.0..=1.0, + }] + .into(), ) + .unwrap() .push_constants(pipeline_layout.clone(), 0, push_constants) + .unwrap() .bind_descriptor_sets( PipelineBindPoint::Graphics, pipeline_layout.clone(), 0, data_descriptor, ) + .unwrap() .bind_vertex_buffers(0, self.buffers_init.clone()) + .unwrap() .draw(written_instances as u32, 1, 0, 0) .unwrap(); - command_buffer_builder.end_render_pass().unwrap(); + command_buffer_builder + .end_render_pass(Default::default()) + .unwrap(); let command_buffer = command_buffer_builder.build().unwrap(); let now = sync::now(self.gfx_queue.device().clone()).boxed(); diff --git a/src/gui/window/scene/note_list_system/mod.rs b/src/gui/window/scene/note_list_system/mod.rs index 29a270e..f5cceaf 100644 --- a/src/gui/window/scene/note_list_system/mod.rs +++ b/src/gui/window/scene/note_list_system/mod.rs @@ -3,7 +3,7 @@ mod notes_render_pass; use std::{cell::UnsafeCell, sync::Arc}; use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; -use vulkano::image::ImageViewAbstract; +use vulkano::image::view::ImageView; use crate::{ gui::{window::keyboard_layout::KeyboardView, GuiRenderer}, @@ -47,7 +47,7 @@ impl NoteRenderer { pub fn draw( &mut self, key_view: &KeyboardView, - final_image: Arc, + final_image: Arc, midi_file: &mut impl MIDIFile, view_range: f64, ) -> RenderResultData { @@ -69,7 +69,7 @@ impl NoteRenderer { let mut columns_view_info = Vec::new(); let border_width = utils::calculate_border_width( - final_image.dimensions().width() as f32, + final_image.image().extent()[0] as f32, key_view.visible_range.len() as f32, ); diff --git a/src/gui/window/scene/note_list_system/notes_render_pass.rs b/src/gui/window/scene/note_list_system/notes_render_pass.rs index 9d8ff72..38f7778 100644 --- a/src/gui/window/scene/note_list_system/notes_render_pass.rs +++ b/src/gui/window/scene/note_list_system/notes_render_pass.rs @@ -5,23 +5,30 @@ use vulkano::{ buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer}, command_buffer::{ allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder, CommandBufferUsage, - RenderPassBeginInfo, SubpassContents, + RenderPassBeginInfo, SubpassBeginInfo, SubpassContents, }, descriptor_set::{ allocator::StandardDescriptorSetAllocator, PersistentDescriptorSet, WriteDescriptorSet, }, device::{Device, Queue}, format::Format, - image::{view::ImageView, AttachmentImage, ImageAccess, ImageViewAbstract}, - memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator}, + image::{view::ImageView, Image, ImageCreateInfo, ImageUsage}, + memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, pipeline::{ graphics::{ + color_blend::{ColorBlendAttachmentState, ColorBlendState}, depth_stencil::DepthStencilState, input_assembly::{InputAssemblyState, PrimitiveTopology}, - vertex_input::Vertex, + multisample::MultisampleState, + rasterization::RasterizationState, + subpass::PipelineSubpassType, + vertex_input::{Vertex, VertexDefinition}, viewport::{Viewport, ViewportState}, + GraphicsPipelineCreateInfo, }, - GraphicsPipeline, Pipeline, PipelineBindPoint, + layout::PipelineDescriptorSetLayoutCreateInfo, + GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout, + PipelineShaderStageCreateInfo, }, render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, sync::{self, future::FenceSignalFuture, GpuFuture}, @@ -58,16 +65,16 @@ struct BufferSet { } fn get_buffer(device: &Arc) -> (Subbuffer<[NoteVertex]>, Subbuffer<[NoteVertex]>) { - let allocator = StandardMemoryAllocator::new_default(device.clone()); + let allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone())); Buffer::new_slice( - &allocator, + allocator.clone(), BufferCreateInfo { usage: BufferUsage::VERTEX_BUFFER, ..Default::default() }, AllocationCreateInfo { - usage: MemoryUsage::Upload, + memory_type_filter: MemoryTypeFilter::PREFER_HOST, ..Default::default() }, NOTE_BUFFER_SIZE * 2, @@ -113,31 +120,33 @@ pub struct NoteRenderPass { render_pass_clear: Arc, render_pass_draw_over: Arc, key_locations: Subbuffer<[[KeyPosition; 256]]>, - depth_buffer: Arc>, - allocator: StandardMemoryAllocator, + depth_buffer: Arc, + allocator: Arc, cb_allocator: StandardCommandBufferAllocator, sd_allocator: StandardDescriptorSetAllocator, } impl NoteRenderPass { pub fn new(renderer: &GuiRenderer) -> NoteRenderPass { - let allocator = StandardMemoryAllocator::new_default(renderer.device.clone()); + let allocator = Arc::new(StandardMemoryAllocator::new_default( + renderer.device.clone(), + )); let gfx_queue = renderer.queue.clone(); let render_pass_clear = vulkano::ordered_passes_renderpass!(gfx_queue.device().clone(), attachments: { final_color: { - load: Clear, - store: Store, format: renderer.format, samples: 1, + load_op: Clear, + store_op: Store, }, depth: { - load: Clear, - store: Store, format: Format::D16_UNORM, samples: 1, + load_op: Clear, + store_op: Store, } }, passes: [ @@ -153,16 +162,16 @@ impl NoteRenderPass { let render_pass_draw_over = vulkano::ordered_passes_renderpass!(gfx_queue.device().clone(), attachments: { final_color: { - load: DontCare, - store: Store, format: renderer.format, samples: 1, + load_op: DontCare, + store_op: Store, }, depth: { - load: DontCare, - store: Store, format: Format::D16_UNORM, samples: 1, + load_op: DontCare, + store_op: Store, } }, passes: [ @@ -176,48 +185,102 @@ impl NoteRenderPass { .unwrap(); let depth_buffer = ImageView::new_default( - AttachmentImage::transient_input_attachment(&allocator, [1, 1], Format::D16_UNORM) - .unwrap(), + Image::new( + allocator.clone(), + ImageCreateInfo { + extent: [1, 1, 1], + format: Format::D16_UNORM, + usage: ImageUsage::SAMPLED, + ..Default::default() + }, + Default::default(), + ) + .unwrap(), ) .unwrap(); let key_locations = Buffer::from_iter( - &allocator, + allocator.clone(), BufferCreateInfo { - usage: BufferUsage::UNIFORM_BUFFER, + usage: BufferUsage::STORAGE_BUFFER + | BufferUsage::TRANSFER_DST + | BufferUsage::VERTEX_BUFFER, ..Default::default() }, AllocationCreateInfo { - usage: MemoryUsage::Upload, + memory_type_filter: MemoryTypeFilter::PREFER_HOST, ..Default::default() }, [[Default::default(); 256]], ) .unwrap(); - let vs = vs::load(gfx_queue.device().clone()).expect("failed to create shader module"); - let fs = fs::load(gfx_queue.device().clone()).expect("failed to create shader module"); - let gs = gs::load(gfx_queue.device().clone()).expect("failed to create shader module"); - - let pipeline_base = GraphicsPipeline::start() - .input_assembly_state(InputAssemblyState::new().topology(PrimitiveTopology::PointList)) - .vertex_input_state(NoteVertex::per_vertex()) - .vertex_shader(vs.entry_point("main").unwrap(), ()) - .geometry_shader(gs.entry_point("main").unwrap(), ()) - .fragment_shader(fs.entry_point("main").unwrap(), ()) - .viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant()) - .depth_stencil_state(DepthStencilState::simple_depth_test()); - - let pipeline_clear = pipeline_base - .clone() - .render_pass(Subpass::from(render_pass_clear.clone(), 0).unwrap()) - .build(gfx_queue.device().clone()) + let vs = vs::load(gfx_queue.device().clone()) + .expect("failed to create shader module") + .entry_point("main") + .unwrap(); + let fs = fs::load(gfx_queue.device().clone()) + .expect("failed to create shader module") + .entry_point("main") + .unwrap(); + let gs = gs::load(gfx_queue.device().clone()) + .expect("failed to create shader module") + .entry_point("main") .unwrap(); - let pipeline_draw_over = pipeline_base - .render_pass(Subpass::from(render_pass_draw_over.clone(), 0).unwrap()) - .build(gfx_queue.device().clone()) + let vertex_input_state = NoteVertex::per_vertex() + .definition(&vs.info().input_interface) .unwrap(); + let stages = [ + PipelineShaderStageCreateInfo::new(vs), + PipelineShaderStageCreateInfo::new(fs), + PipelineShaderStageCreateInfo::new(gs), + ]; + let layout = PipelineLayout::new( + renderer.device.clone(), + PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) + .into_pipeline_layout_create_info(renderer.device.clone()) + .unwrap(), + ) + .unwrap(); + let subpass = Subpass::from(render_pass_clear.clone(), 0).unwrap(); + + let mut create_info = GraphicsPipelineCreateInfo { + stages: stages.into_iter().collect(), + vertex_input_state: Some(vertex_input_state), + input_assembly_state: Some(InputAssemblyState { + topology: PrimitiveTopology::PointList, + ..Default::default() + }), + viewport_state: Some(ViewportState { + viewports: [Viewport { + offset: [0.0, 0.0], + extent: [1280.0, 720.0], + depth_range: 0.0..=1.0, + }] + .into_iter() + .collect(), + ..Default::default() + }), + rasterization_state: Some(RasterizationState::default()), + multisample_state: Some(MultisampleState::default()), + color_blend_state: Some(ColorBlendState::with_attachment_states( + subpass.num_color_attachments(), + ColorBlendAttachmentState::default(), + )), + depth_stencil_state: Some(DepthStencilState::default()), + subpass: Some(subpass.into()), + ..GraphicsPipelineCreateInfo::layout(layout) + }; + + let pipeline_clear = + GraphicsPipeline::new(renderer.device.clone(), None, create_info.clone()).unwrap(); + + create_info.subpass = Some(PipelineSubpassType::BeginRenderPass( + Subpass::from(render_pass_draw_over.clone(), 0).unwrap(), + )); + let pipeline_draw_over = + GraphicsPipeline::new(renderer.device.clone(), None, create_info).unwrap(); NoteRenderPass { gfx_queue, @@ -233,24 +296,32 @@ impl NoteRenderPass { renderer.device.clone(), Default::default(), ), - sd_allocator: StandardDescriptorSetAllocator::new(renderer.device.clone()), + sd_allocator: StandardDescriptorSetAllocator::new( + renderer.device.clone(), + Default::default(), + ), } } pub fn draw( &mut self, - final_image: Arc, + final_image: Arc, key_view: &KeyboardView, view_range: f32, mut fill_buffer: impl FnMut(&Subbuffer<[NoteVertex]>) -> NotePassStatus, ) { - let img_dims = final_image.image().dimensions().width_height(); - if self.depth_buffer.image().dimensions().width_height() != img_dims { + let img_dims = final_image.image().extent(); + if self.depth_buffer.image().extent() != img_dims { self.depth_buffer = ImageView::new_default( - AttachmentImage::transient_input_attachment( - &self.allocator, - img_dims, - Format::D16_UNORM, + Image::new( + self.allocator.clone(), + ImageCreateInfo { + extent: [img_dims[0], img_dims[1], 1], + format: Format::D16_UNORM, + usage: ImageUsage::SAMPLED, + ..Default::default() + }, + Default::default(), ) .unwrap(), ) @@ -321,20 +392,25 @@ impl NoteRenderPass { let pipeline_layout = pipeline.layout(); let desc_layout = pipeline_layout.set_layouts().first().unwrap(); + let write_descriptor_set = WriteDescriptorSet::buffer(0, self.key_locations.clone()); let set = PersistentDescriptorSet::new( &self.sd_allocator, desc_layout.clone(), - [WriteDescriptorSet::buffer(0, self.key_locations.clone())], + [write_descriptor_set], + [], ) .unwrap(); + let mut subpassbegininfo = SubpassBeginInfo::default(); + subpassbegininfo.contents = SubpassContents::Inline; + command_buffer_builder .begin_render_pass( RenderPassBeginInfo { clear_values: clears, ..RenderPassBeginInfo::framebuffer(framebuffer) }, - SubpassContents::Inline, + subpassbegininfo, ) .unwrap(); @@ -346,26 +422,34 @@ impl NoteRenderPass { command_buffer_builder .bind_pipeline_graphics(pipeline.clone()) + .unwrap() .set_viewport( 0, - [Viewport { - origin: [0.0, 0.0], - dimensions: [img_dims[0] as f32, img_dims[1] as f32], - depth_range: 0.0..1.0, - }], + vec![Viewport { + offset: [0.0, 0.0], + extent: [img_dims[0] as f32, img_dims[1] as f32], + depth_range: 0.0..=1.0, + }] + .into(), ) + .unwrap() .push_constants(pipeline_layout.clone().clone(), 0, push_constants) + .unwrap() .bind_descriptor_sets( PipelineBindPoint::Graphics, pipeline_layout.clone(), 0, set.clone(), ) + .unwrap() .bind_vertex_buffers(0, buffer.clone()) + .unwrap() .draw(items_to_render, 1, 0, 0) .unwrap(); - command_buffer_builder.end_render_pass().unwrap(); + command_buffer_builder + .end_render_pass(Default::default()) + .unwrap(); let command_buffer = command_buffer_builder.build().unwrap(); if let Some(prev_future) = prev_future.take() { diff --git a/src/gui/window/settings.rs b/src/gui/window/settings.rs new file mode 100644 index 0000000..a651360 --- /dev/null +++ b/src/gui/window/settings.rs @@ -0,0 +1,181 @@ +use std::{ + path::PathBuf, + sync::{Arc, RwLock}, +}; + +use soundfonts::EguiSFList; + +use crate::{ + audio_playback::WasabiAudioPlayer, + settings::WasabiSettings, + state::{SettingsTab, WasabiState}, +}; + +mod midi; +mod soundfonts; +mod synth; +mod visual; + +const CATEG_SPACE: f32 = 12.0; +const SPACING: [f32; 2] = [40.0, 12.0]; + +#[derive(Clone)] +struct FilePalette { + pub path: PathBuf, + pub selected: bool, +} + +#[derive(Clone)] +struct MidiDevice { + pub name: String, + pub selected: bool, +} + +pub struct SettingsWindow { + palettes: Vec, + midi_devices: Vec, + sf_list: EguiSFList, +} + +impl SettingsWindow { + pub fn new(settings: &WasabiSettings) -> Self { + let mut sf_list = EguiSFList::new(); + for sf in settings.synth.soundfonts.iter() { + sf_list.add_item(sf.clone()).unwrap_or_default(); + } + + Self { + palettes: Vec::new(), + midi_devices: Vec::new(), + sf_list, + } + } + + pub fn show( + &mut self, + ctx: &egui::Context, + settings: &mut WasabiSettings, + state: &mut WasabiState, + synth: Arc>, + ) { + let frame = + egui::Frame::inner_margin(egui::Frame::window(ctx.style().as_ref()), super::WIN_MARGIN); + + egui::Window::new("Settings") + .resizable(true) + .collapsible(false) + .title_bar(true) + .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) + .movable(false) + .enabled(true) + .frame(frame) + .default_size([600.0, 400.0]) + .min_size([500.0, 200.0]) + .open(&mut state.show_settings) + .show(ctx, |ui| { + egui::TopBottomPanel::top("settings_tab_selector") + .resizable(false) + .show_inside(ui, |ui| { + ui.style_mut() + .text_styles + .get_mut(&egui::TextStyle::Button) + .unwrap() + .size = 20.0; + + ui.horizontal(|ui| { + ui.selectable_value( + &mut state.settings_tab, + SettingsTab::Visual, + "\u{1f4bb} Visual", + ); + ui.selectable_value( + &mut state.settings_tab, + SettingsTab::Midi, + "\u{1f3b5} MIDI", + ); + ui.selectable_value( + &mut state.settings_tab, + SettingsTab::Synth, + "\u{1f3b9} Synth", + ); + ui.selectable_value( + &mut state.settings_tab, + SettingsTab::SoundFonts, + "\u{1f50a} SoundFonts", + ); + }); + ui.add_space(4.0); + }); + + egui::TopBottomPanel::bottom("settings_save_panel") + .resizable(false) + .show_inside(ui, |ui| { + ui.add_space(4.0); + ui.centered_and_justified(|ui| { + if ui.button("Save").clicked() { + settings.save_to_file(); + } + }); + }); + + let width = ui.available_width() - 40.0; + egui::CentralPanel::default().show_inside(ui, |ui| { + egui::ScrollArea::vertical().animated(true).show(ui, |ui| { + match state.settings_tab { + SettingsTab::Visual => self.show_visual_settings(ui, settings, width), + SettingsTab::Midi => self.show_midi_settings(ui, settings, width), + SettingsTab::Synth => { + self.show_synth_settings(ui, settings, width, synth) + } + SettingsTab::SoundFonts => { + self.show_soundfont_settings(ui, settings, width, synth) + } + } + }) + }); + }); + } + + pub fn load_palettes(&mut self) { + self.palettes.clear(); + + let files = std::fs::read_dir(WasabiSettings::get_palettes_dir()).unwrap(); + + for file in files.filter_map(|i| i.ok()) { + if let Ok(ftype) = file.file_type() { + if ftype.is_file() { + self.palettes.push(FilePalette { + path: file.path(), + selected: false, + }); + } + } + } + } + + pub fn load_midi_devices(&mut self, settings: &mut WasabiSettings) { + self.midi_devices.clear(); + if let Ok(con) = midir::MidiOutput::new("wasabi") { + let ports = con.ports(); + for port in ports.iter() { + if let Ok(name) = con.port_name(&port) { + self.midi_devices.push(MidiDevice { + name, + selected: false, + }); + } + } + } + + let saved = settings.synth.midi_device.clone(); + if let Some(found) = self.midi_devices.iter_mut().find(|d| d.name == saved) { + found.selected = true; + return; + } + + if !self.midi_devices.is_empty() { + self.midi_devices[0].selected = true; + settings.synth.midi_device = self.midi_devices[0].name.clone(); + } + } +} diff --git a/src/gui/window/settings/midi.rs b/src/gui/window/settings/midi.rs new file mode 100644 index 0000000..5eeea9c --- /dev/null +++ b/src/gui/window/settings/midi.rs @@ -0,0 +1,149 @@ +use egui_extras::{Column, TableBuilder}; + +use crate::settings::{Colors, MidiParsing, WasabiSettings}; + +use super::SettingsWindow; + +impl SettingsWindow { + pub fn show_midi_settings( + &mut self, + ui: &mut egui::Ui, + settings: &mut WasabiSettings, + width: f32, + ) { + ui.heading("Settings"); + egui::Grid::new("midi_settings_grid") + .num_columns(2) + .spacing(super::SPACING) + .striped(true) + .min_col_width(width / 2.0) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.label("MIDI Parsing Algorithm:"); + ui.monospace("\u{2139}").on_hover_text( + "\ + - Cake\n\ + \0 The most efficient loading and displaying algorithm.\n\ + \0 The notes will be stored in binary trees and will be\n\ + \0 displayed dynamically.\n\ + - Standard (RAM)\n\ + \0 The MIDI will be loaded in the RAM and all the notes\n\ + \0 will be rendered normally by the GPU.\n\ + - Standard (Live)\n\ + \0 The MIDI will be streamed live from the disk and all\n\ + \0 the notes will be rendered normally by the GPU.\ + ", + ); + }); + egui::ComboBox::from_id_salt("midi_parsing_select") + .selected_text(settings.midi.parsing.as_str()) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut settings.midi.parsing, + MidiParsing::Cake, + MidiParsing::Cake.as_str(), + ); + ui.selectable_value( + &mut settings.midi.parsing, + MidiParsing::Ram, + MidiParsing::Ram.as_str(), + ); + ui.selectable_value( + &mut settings.midi.parsing, + MidiParsing::Live, + MidiParsing::Live.as_str(), + ); + }); + ui.end_row(); + }); + ui.vertical_centered(|ui| { + ui.small("Changes to this setting will be applied when a new MIDI is loaded."); + }); + + ui.horizontal(|ui| ui.add_space(width + 40.0)); + ui.add_space(super::CATEG_SPACE); + ui.heading("Color Palette"); + egui::Frame::default() + .rounding(egui::Rounding::same(8.0)) + .stroke(ui.style().visuals.widgets.noninteractive.bg_stroke) + .show(ui, |ui| { + TableBuilder::new(ui) + .striped(true) + .cell_layout(egui::Layout::centered_and_justified( + egui::Direction::LeftToRight, + )) + .resizable(true) + .column(Column::exact(width).resizable(false)) + .body(|mut body| { + let row_height = super::CATEG_SPACE * 3.0; + body.row(row_height, |mut row| { + row.col(|ui| { + if ui + .selectable_label( + settings.midi.colors == Colors::Rainbow, + Colors::Rainbow.as_str(), + ) + .clicked() + { + settings.midi.colors = Colors::Rainbow; + } + }); + }); + body.row(row_height, |mut row| { + row.col(|ui| { + if ui + .selectable_label( + settings.midi.colors == Colors::Random, + Colors::Random.as_str(), + ) + .clicked() + { + settings.midi.colors = Colors::Random; + } + }); + }); + let mut temp = self.palettes.clone(); + for i in temp.iter_mut() { + i.selected = false; + } + let mut changed = false; + for (i, palette) in self.palettes.iter_mut().enumerate() { + body.row(row_height, |mut row| { + row.col(|ui| { + if ui + .selectable_label( + settings.midi.colors == Colors::Palette + && palette.selected, + palette + .path + .file_name() + .unwrap_or_default() + .to_str() + .unwrap_or_default(), + ) + .clicked() + { + settings.midi.colors = Colors::Palette; + temp[i].selected = true; + settings.midi.palette_path = palette.path.clone(); + changed = true; + } + }); + }); + } + if changed { + self.palettes = temp; + } + }); + }); + ui.add_space(4.0); + ui.horizontal(|ui| { + if ui.button("Refresh List").clicked() { + self.load_palettes(); + } + if ui.button("Open Palettes Directory").clicked() { + open::that(WasabiSettings::get_palettes_dir()).unwrap_or_default(); + } + }); + } +} diff --git a/src/gui/window/settings/soundfonts.rs b/src/gui/window/settings/soundfonts.rs new file mode 100644 index 0000000..c28f2ba --- /dev/null +++ b/src/gui/window/settings/soundfonts.rs @@ -0,0 +1,22 @@ +use std::sync::{Arc, RwLock}; + +use crate::{audio_playback::WasabiAudioPlayer, settings::WasabiSettings}; + +use super::SettingsWindow; + +mod list; +pub use list::*; +mod cfg; +pub use cfg::*; + +impl SettingsWindow { + pub fn show_soundfont_settings( + &mut self, + ui: &mut egui::Ui, + settings: &mut WasabiSettings, + width: f32, + synth: Arc>, + ) { + self.sf_list.show(ui, settings, width, synth); + } +} diff --git a/src/gui/window/settings/soundfonts/cfg.rs b/src/gui/window/settings/soundfonts/cfg.rs new file mode 100644 index 0000000..a183d62 --- /dev/null +++ b/src/gui/window/settings/soundfonts/cfg.rs @@ -0,0 +1,122 @@ +use crate::gui::window::WIN_MARGIN; + +use super::{SFFormat, SFListItem}; +use egui::{Context, Window}; +use xsynth_core::soundfont::Interpolator; + +#[derive(Clone)] +pub struct SoundfontConfigWindow { + pub visible: bool, + id: usize, +} + +impl SoundfontConfigWindow { + pub fn new(id: usize) -> Self { + Self { visible: true, id } + } + + pub fn id(&self) -> usize { + self.id + } + + pub fn show(&mut self, ctx: &Context, item: &mut SFListItem) { + let title = if let Some(path) = item.item.path.file_name() { + format!("Config for {path:?}") + } else { + format!("Config for {}", self.id) + }; + + let frame = + egui::Frame::inner_margin(egui::Frame::window(ctx.style().as_ref()), WIN_MARGIN); + + Window::new(title) + .id(egui::Id::new(self.id)) + .collapsible(false) + .title_bar(true) + .enabled(true) + .frame(frame) + .open(&mut self.visible) + .show(ctx, |ui| { + let col_width = 80.0; + + ui.heading("Instrument"); + ui.separator(); + egui::Grid::new("sfconfig_window_instr") + .num_columns(2) + .min_col_width(col_width) + .show(ui, |ui| { + let mut modify = item.item.options.bank.is_some(); + + ui.label("Override Instrument: "); + let allow_override = !(item.format == SFFormat::Sfz); + ui.add_enabled(allow_override, egui::Checkbox::without_text(&mut modify)); + ui.end_row(); + + if modify && item.item.options.bank.is_none() { + item.item.options.bank = Some(0); + item.item.options.preset = Some(0); + } else if !modify { + item.item.options.bank = None; + item.item.options.preset = None; + } + + let mut bank = item.item.options.bank.unwrap_or(0); + + ui.label("Bank: "); + ui.add_enabled( + modify, + egui::DragValue::new(&mut bank).speed(1).range(0..=128), + ); + ui.end_row(); + + if bank != item.item.options.bank.unwrap_or(0) { + item.item.options.bank = Some(bank) + } + + let mut preset = item.item.options.preset.unwrap_or(0); + + ui.label("Preset: "); + ui.add_enabled( + modify, + egui::DragValue::new(&mut preset).speed(1).range(0..=127), + ); + ui.end_row(); + + if preset != item.item.options.preset.unwrap_or(0) { + item.item.options.preset = Some(preset) + } + }); + + ui.heading("Settings"); + ui.separator(); + egui::Grid::new("sfconfig_window_settings") + .num_columns(2) + .min_col_width(col_width) + .show(ui, |ui| { + // ui.label("Linear Release Envelope: "); + // ui.checkbox(&mut item.init.linear_release, ""); + // ui.end_row(); + + let interp = ["Nearest Neighbor", "Linear"]; + let mut interp_idx = item.item.options.interpolator as usize; + + ui.label("Interpolation:"); + egui::ComboBox::from_id_salt("interpolation").show_index( + ui, + &mut interp_idx, + interp.len(), + |i| interp[i].to_owned(), + ); + ui.end_row(); + + if interp_idx != item.item.options.interpolator as usize { + match interp_idx { + 0 => item.item.options.interpolator = Interpolator::Nearest, + 1 => item.item.options.interpolator = Interpolator::Linear, + _ => item.item.options.interpolator = Interpolator::Nearest, + }; + } + }); + }); + } +} diff --git a/src/gui/window/settings/soundfonts/list.rs b/src/gui/window/settings/soundfonts/list.rs new file mode 100644 index 0000000..6d0b943 --- /dev/null +++ b/src/gui/window/settings/soundfonts/list.rs @@ -0,0 +1,391 @@ +use std::{ + path::PathBuf, + sync::{Arc, RwLock}, + thread, +}; + +use crossbeam_channel::{Receiver, Sender}; +use egui::WidgetText; +use egui_extras::{Column, TableBuilder}; +use serde::{Deserialize, Serialize}; + +use crate::{ + audio_playback::WasabiAudioPlayer, + settings::{WasabiSettings, WasabiSoundfont}, +}; + +use super::SoundfontConfigWindow; + +#[derive(Default, Clone, PartialEq, Serialize, Deserialize)] +pub enum SFFormat { + #[default] + Sfz, + Sf2, +} + +#[derive(Default, Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct SFListItem { + pub item: WasabiSoundfont, + pub id: usize, + pub selected: bool, + pub format: SFFormat, +} + +pub struct EguiSFList { + list: Vec, + id_count: usize, + + sf_picker: (Sender, Receiver), + sf_cfg_win: Vec, +} + +impl EguiSFList { + pub fn new() -> Self { + let sf_picker = crossbeam_channel::unbounded(); + + Self { + list: Vec::new(), + id_count: 0, + sf_picker, + sf_cfg_win: Vec::new(), + } + } + + pub fn add_item(&mut self, sf: WasabiSoundfont) -> Result<(), String> { + let err = Err(format!( + "The selected soundfont does not have the correct format: {:?}", + sf.path + )); + + if let Some(ext) = sf.path.extension() { + let format = match ext.to_str().unwrap().to_lowercase().as_str() { + "sfz" => SFFormat::Sfz, + "sf2" => SFFormat::Sf2, + _ => return err, + }; + + let item = SFListItem { + item: sf, + id: self.id_count, + selected: false, + format, + }; + self.list.push(item); + self.id_count += 1; + return Ok(()); + } + + err + } + + pub fn add_path(&mut self, path: PathBuf) -> Result<(), String> { + if !path.exists() { + return Err(format!("File not found: {:?}", path)); + } + + let item = WasabiSoundfont { + path, + enabled: true, + options: Default::default(), + }; + + self.add_item(item) + } + + pub fn select_all(&mut self) { + self.list = self + .list + .clone() + .into_iter() + .map(|mut item| { + item.selected = true; + item + }) + .collect(); + } + + pub fn remove_selected_items(&mut self) { + self.list = self + .list + .clone() + .into_iter() + .filter(|item| !item.selected) + .collect(); + + // I'm bored to make it close only the windows needed, so instead I'll close all of them + self.sf_cfg_win.clear(); + } + + pub fn clear(&mut self) { + self.list.clear(); + self.sf_cfg_win.clear(); + } + + pub fn as_vec(&self) -> Vec { + self.list.iter().map(|sf| sf.item.clone()).collect() + } + + pub fn show( + &mut self, + ui: &mut egui::Ui, + settings: &mut WasabiSettings, + width: f32, + synth: Arc>, + ) { + let events = ui.input(|i| i.events.clone()); + for event in &events { + if let egui::Event::Key { + key, + modifiers, + pressed, + .. + } = event + { + match *key { + egui::Key::A => { + if *pressed && modifiers.ctrl { + self.select_all(); + } + } + egui::Key::Delete => { + self.remove_selected_items(); + } + _ => {} + } + } + } + + { + let recv = self.sf_picker.1.clone(); + if !recv.is_empty() { + for path in recv { + //state.last_location = path.clone(); + if path.is_file() { + if let Err(error) = self.add_path(path.clone()) { + let title = if let Some(filen) = path.file_name() { + format!( + "There was an error adding \"{}\" to the list.", + filen.to_str().unwrap() + ) + } else { + "There was an error adding the selected soundfont to the list." + .to_string() + }; + // TODO: errors + } + } + break; + } + } + } + + // if !ui.input(|i| i.raw.dropped_files.is_empty()) { + // let dropped_files = ui.input(|i| { + // i.raw + // .dropped_files + // .clone() + // .iter() + // .map(|file| file.path.as_ref().unwrap().clone()) + // .collect::>() + // }); + + // for file in dropped_files { + // if let Err(error) = self.add_item(file.clone()) { + // let title = if let Some(filen) = file.file_name() { + // // Not a safe unwrap but things must be very wrong for it to panic so idc + // format!( + // "There was an error adding \"{}\" to the list.", + // filen.to_str().unwrap() + // ) + // } else { + // "There was an error adding the selected soundfont to the list.".to_string() + // }; + // // TODO: errors + // } + // } + // } + + self.sf_cfg_win = self + .sf_cfg_win + .clone() + .into_iter() + .filter(|item| item.visible) + .collect(); + + for cfg in self.sf_cfg_win.iter_mut() { + let index = self.list.iter().position(|item| item.id == cfg.id()); + if let Some(index) = index { + cfg.show(ui.ctx(), &mut self.list[index]); + } + } + + egui::TopBottomPanel::bottom("bottom_panel") + .resizable(false) + .show_inside(ui, |ui| { + ui.add_space(5.0); + ui.horizontal(|ui| { + if ui + .button( + WidgetText::from(" \u{2795} ") + .text_style(egui::TextStyle::Name("monospace big".into())), + ) + .on_hover_text("Add SoundFont(s)") + .clicked() + { + let sender = self.sf_picker.0.clone(); + //let last_location = state.last_location.clone(); + + thread::spawn(move || { + let midi_path = rfd::FileDialog::new() + .add_filter("Supported SoundFonts", &["sfz", "SFZ", "sf2", "SF2"]) + //.set_directory(last_location.parent().unwrap_or(Path::new("./"))) + .pick_file(); + + if let Some(midi_path) = midi_path { + sender.send(midi_path).unwrap_or_default(); + } + }); + } + if ui + .button( + WidgetText::from(" \u{2796} ") + .text_style(egui::TextStyle::Name("monospace big".into())), + ) + .on_hover_text("Remove Selected") + .clicked() + { + self.remove_selected_items(); + } + if ui + .button( + WidgetText::from(" \u{2716} ") + .text_style(egui::TextStyle::Name("monospace big".into())), + ) + .on_hover_text("Clear List") + .clicked() + { + self.clear(); + } + if ui + .button( + WidgetText::from(" \u{1F503} ") + .text_style(egui::TextStyle::Name("monospace big".into())), + ) + .on_hover_text("Apply SoundFont List") + .clicked() + { + synth + .write() + .unwrap() + .set_soundfonts(&settings.synth.soundfonts); + } + }); + ui.small("Loading order is top to bottom. Supported formats: SFZ, SF2"); + }); + + egui::ScrollArea::both().show(ui, |ui| { + let events = ui.input(|i| i.events.clone()); + for event in &events { + if let egui::Event::Key { + key, + modifiers, + pressed, + .. + } = event + { + match *key { + egui::Key::A => { + if *pressed && modifiers.ctrl { + self.select_all(); + } + } + egui::Key::Delete => { + self.remove_selected_items(); + } + _ => {} + } + } + } + + TableBuilder::new(ui) + .striped(true) + .cell_layout(egui::Layout::centered_and_justified( + egui::Direction::LeftToRight, + )) + .resizable(true) + .column(Column::exact(20.0).resizable(false)) + .column(Column::initial(width - 200.0).at_least(50.0).clip(true)) + .columns(Column::auto().at_least(40.0).clip(true), 2) + .column(Column::auto().at_least(40.0).clip(true).resizable(false)) + .header(20.0, |mut header| { + header.col(|_ui| {}); + header.col(|ui| { + ui.strong("Filename"); + }); + header.col(|ui| { + ui.strong("Format"); + }); + header.col(|ui| { + ui.strong("Bank"); + }); + header.col(|ui| { + ui.strong("Preset"); + }); + }) + .body(|mut body| { + let row_height = super::super::CATEG_SPACE * 3.0; + for item in self.list.iter_mut() { + body.row(row_height, |mut row| { + row.col(|ui| { + ui.checkbox(&mut item.item.enabled, ""); + }); + row.col(|ui| { + let selectable = if let Some(path) = item.item.path.to_str() { + ui.selectable_label(item.selected, path) + } else { + ui.selectable_label(item.selected, "error") + }; + + if selectable.clicked() { + item.selected = !item.selected; + } + if selectable.double_clicked() + && !self.sf_cfg_win.iter().any(|cfg| cfg.id() == item.id) + { + self.sf_cfg_win.push(SoundfontConfigWindow::new(item.id)) + } + }); + row.col(|ui| { + ui.label(match item.format { + SFFormat::Sfz => "SFZ", + SFFormat::Sf2 => "SF2", + }); + }); + + let bank_txt = if let Some(bank) = item.item.options.bank { + format!("{}", bank) + } else { + "None".to_owned() + }; + row.col(|ui| { + ui.label(bank_txt.to_string()); + }); + + let preset_txt = if let Some(preset) = item.item.options.preset { + format!("{}", preset) + } else { + "None".to_owned() + }; + row.col(|ui| { + ui.label(preset_txt.to_string()); + }); + }); + } + }); + ui.allocate_space(ui.available_size()); + }); + + settings.synth.soundfonts = self.as_vec(); + } +} diff --git a/src/gui/window/settings/synth.rs b/src/gui/window/settings/synth.rs new file mode 100644 index 0000000..1cc8c09 --- /dev/null +++ b/src/gui/window/settings/synth.rs @@ -0,0 +1,96 @@ +use std::sync::{Arc, RwLock}; + +use crate::{ + audio_playback::WasabiAudioPlayer, + gui::window::GuiWasabiWindow, + settings::{Synth, WasabiSettings}, +}; + +use super::SettingsWindow; + +mod kdmapi; +mod mididevice; +mod xsynth; + +impl SettingsWindow { + pub fn show_synth_settings( + &mut self, + ui: &mut egui::Ui, + settings: &mut WasabiSettings, + width: f32, + synth: Arc>, + ) { + egui::Grid::new("synth_settings_grid") + .num_columns(2) + .spacing(super::SPACING) + .striped(true) + .min_col_width(width / 2.0) + .show(ui, |ui| { + let synth_prev = settings.synth.synth; + ui.label("Synthesizer:"); + ui.horizontal(|ui| { + egui::ComboBox::from_id_salt("synth_select") + .selected_text(settings.synth.synth.as_str()) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut settings.synth.synth, + Synth::XSynth, + Synth::XSynth.as_str(), + ); + ui.selectable_value( + &mut settings.synth.synth, + Synth::Kdmapi, + Synth::Kdmapi.as_str(), + ); + ui.selectable_value( + &mut settings.synth.synth, + Synth::MidiDevice, + Synth::MidiDevice.as_str(), + ); + ui.selectable_value( + &mut settings.synth.synth, + Synth::None, + Synth::None.as_str(), + ); + }); + + if ui + .button( + egui::WidgetText::from(" \u{1F503} ") + .text_style(egui::TextStyle::Name("monospace big".into())), + ) + .on_hover_text("Reload Synth") + .clicked() + { + synth + .write() + .unwrap() + .switch(GuiWasabiWindow::create_synth(settings)); + } + }); + ui.end_row(); + + if settings.synth.synth != synth_prev { + let new_player = GuiWasabiWindow::create_synth(settings); + synth.write().unwrap().switch(new_player); + } + }); + + ui.add_space(8.0); + ui.vertical_centered(|ui| { + ui.small("Options marked with (*) will apply when the synth is reloaded."); + }); + + ui.add_space(super::CATEG_SPACE); + ui.heading("Synth Settings"); + + match settings.synth.synth { + Synth::XSynth => self.show_xsynth_settings(ui, settings, width, synth), + Synth::Kdmapi => self.show_kdmapi_settings(ui, settings, width), + Synth::MidiDevice => self.show_mididevice_settings(ui, settings, width, synth), + Synth::None => { + ui.label("No Settings"); + } + } + } +} diff --git a/src/gui/window/settings/synth/kdmapi.rs b/src/gui/window/settings/synth/kdmapi.rs new file mode 100644 index 0000000..0a95268 --- /dev/null +++ b/src/gui/window/settings/synth/kdmapi.rs @@ -0,0 +1,23 @@ +use crate::settings::WasabiSettings; + +use super::SettingsWindow; + +impl SettingsWindow { + pub fn show_kdmapi_settings( + &mut self, + ui: &mut egui::Ui, + settings: &mut WasabiSettings, + width: f32, + ) { + egui::Grid::new("kdmapi_settings_grid") + .num_columns(2) + .spacing(super::super::SPACING) + .striped(true) + .min_col_width(width / 2.0) + .show(ui, |ui| { + ui.label("Use the driver's soundfont list*:"); + ui.checkbox(&mut settings.synth.kdmapi.use_om_sflist, ""); + ui.end_row(); + }); + } +} diff --git a/src/gui/window/settings/synth/mididevice.rs b/src/gui/window/settings/synth/mididevice.rs new file mode 100644 index 0000000..4f2a0fa --- /dev/null +++ b/src/gui/window/settings/synth/mididevice.rs @@ -0,0 +1,66 @@ +use std::sync::{Arc, RwLock}; + +use egui_extras::{Column, TableBuilder}; + +use crate::{ + audio_playback::WasabiAudioPlayer, gui::window::GuiWasabiWindow, settings::WasabiSettings, +}; + +use super::SettingsWindow; + +impl SettingsWindow { + pub fn show_mididevice_settings( + &mut self, + ui: &mut egui::Ui, + settings: &mut WasabiSettings, + width: f32, + synth: Arc>, + ) { + egui::Frame::default() + .rounding(egui::Rounding::same(8.0)) + .stroke(ui.style().visuals.widgets.noninteractive.bg_stroke) + .show(ui, |ui| { + TableBuilder::new(ui) + .striped(true) + .cell_layout(egui::Layout::centered_and_justified( + egui::Direction::LeftToRight, + )) + .resizable(true) + .column(Column::exact(width).resizable(false)) + .body(|mut body| { + let row_height = super::super::CATEG_SPACE * 3.0; + + let mut temp = self.midi_devices.clone(); + for i in temp.iter_mut() { + i.selected = false; + } + let mut changed = false; + for (i, device) in self.midi_devices.iter_mut().enumerate() { + body.row(row_height, |mut row| { + row.col(|ui| { + if ui + .selectable_label(device.selected, device.name.clone()) + .clicked() + { + temp[i].selected = true; + settings.synth.midi_device = device.name.clone(); + changed = true; + } + }); + }); + } + if changed { + self.midi_devices = temp; + synth + .write() + .unwrap() + .switch(GuiWasabiWindow::create_synth(settings)); + } + }); + }); + ui.add_space(4.0); + if ui.button("Refresh List").clicked() { + self.load_midi_devices(settings); + } + } +} diff --git a/src/gui/window/settings/synth/xsynth.rs b/src/gui/window/settings/synth/xsynth.rs new file mode 100644 index 0000000..05fc5c6 --- /dev/null +++ b/src/gui/window/settings/synth/xsynth.rs @@ -0,0 +1,102 @@ +use std::sync::{Arc, RwLock}; + +use xsynth_realtime::ThreadCount; + +use crate::{audio_playback::WasabiAudioPlayer, settings::WasabiSettings}; + +use super::SettingsWindow; + +impl SettingsWindow { + pub fn show_xsynth_settings( + &mut self, + ui: &mut egui::Ui, + settings: &mut WasabiSettings, + width: f32, + synth: Arc>, + ) { + egui::Grid::new("xsynth_settings_grid") + .num_columns(2) + .spacing(super::super::SPACING) + .striped(true) + .min_col_width(width / 2.0) + .show(ui, |ui| { + let layer_limit_prev = settings.synth.xsynth.limit_layers; + + ui.label("Enable Layer Limiting: "); + ui.checkbox(&mut settings.synth.xsynth.limit_layers, ""); + ui.end_row(); + + let layer_count_prev = settings.synth.xsynth.layers; + + ui.horizontal(|ui| { + ui.label("Layer Limit:"); + ui.monospace("\u{2139}") + .on_hover_text("One layer is one voice per key per channel."); + }); + ui.add_enabled( + settings.synth.xsynth.limit_layers, + egui::DragValue::new(&mut settings.synth.xsynth.layers) + .speed(1) + .range(1..=usize::MAX), + ); + ui.end_row(); + + if settings.synth.xsynth.layers != layer_count_prev + || layer_limit_prev != settings.synth.xsynth.limit_layers + { + synth.write().unwrap().configure(&settings.synth); + } + + let buffer_prev = settings.synth.xsynth.config.render_window_ms; + ui.label("Render Buffer (ms):"); + ui.add( + egui::DragValue::new(&mut settings.synth.xsynth.config.render_window_ms) + .speed(0.1) + .range(0.0001..=1000.0), + ); + ui.end_row(); + if settings.synth.xsynth.config.render_window_ms != buffer_prev { + synth.write().unwrap().configure(&settings.synth); + } + + ui.label("Ignore velocities between:"); + let mut lovel = *settings.synth.xsynth.config.ignore_range.start(); + let mut hivel = *settings.synth.xsynth.config.ignore_range.end(); + ui.horizontal(|ui| { + ui.add(egui::DragValue::new(&mut lovel).speed(1).range(0..=127)); + ui.label("and"); + ui.add(egui::DragValue::new(&mut hivel).speed(1).range(lovel..=127)); + }); + ui.end_row(); + if lovel != *settings.synth.xsynth.config.ignore_range.start() + || hivel != *settings.synth.xsynth.config.ignore_range.end() + { + settings.synth.xsynth.config.ignore_range = lovel..=hivel; + synth.write().unwrap().configure(&settings.synth); + } + + ui.label("Fade out voice when killing it*: "); + ui.checkbox( + &mut settings + .synth + .xsynth + .config + .channel_init_options + .fade_out_killing, + "", + ); + ui.end_row(); + + let mut threading = + settings.synth.xsynth.config.multithreading == ThreadCount::Auto; + ui.label("Enable multithreading*: "); + ui.checkbox(&mut threading, ""); + ui.end_row(); + if threading { + settings.synth.xsynth.config.multithreading = ThreadCount::Auto; + } else { + settings.synth.xsynth.config.multithreading = ThreadCount::None; + } + }); + } +} diff --git a/src/gui/window/settings/visual.rs b/src/gui/window/settings/visual.rs new file mode 100644 index 0000000..6ba4e00 --- /dev/null +++ b/src/gui/window/settings/visual.rs @@ -0,0 +1,175 @@ +use egui::WidgetText; +use egui_extras::{Column, TableBuilder}; + +use crate::settings::WasabiSettings; + +use super::SettingsWindow; + +impl SettingsWindow { + pub fn show_visual_settings( + &mut self, + ui: &mut egui::Ui, + settings: &mut WasabiSettings, + width: f32, + ) { + ui.heading("General"); + egui::Grid::new("general_visual_settings_grid") + .num_columns(2) + .spacing(super::SPACING) + .striped(true) + .min_col_width(width / 2.0) + .show(ui, |ui| { + ui.label("Check for updates on launch:"); + ui.checkbox(&mut settings.gui.check_for_updates, ""); + ui.end_row(); + + ui.label("FPS Limit:"); + ui.add( + egui::DragValue::new(&mut settings.gui.fps_limit) + .speed(1) + .range(0..=usize::MAX), + ); + ui.end_row(); + + ui.label("Skip Control:"); + ui.add( + egui::DragValue::new(&mut settings.gui.skip_control) + .speed(0.5) + .range(0.0..=f64::MAX), + ); + ui.end_row(); + + ui.label("Speed Control:"); + ui.add( + egui::DragValue::new(&mut settings.gui.speed_control) + .speed(0.5) + .range(0.0..=f64::MAX), + ); + ui.end_row(); + }); + + ui.add_space(super::CATEG_SPACE); + ui.heading("Scene"); + + egui::Grid::new("scene_visual_settings_grid") + .num_columns(2) + .spacing(super::SPACING) + .striped(true) + .min_col_width(width / 2.0) + .show(ui, |ui| { + ui.label("Background Color: "); + ui.color_edit_button_srgba(&mut settings.scene.bg_color); + ui.end_row(); + + ui.label("Bar Color: "); + ui.color_edit_button_srgba(&mut settings.scene.bar_color); + ui.end_row(); + + ui.label("Keyboard Range: "); + let mut firstkey = *settings.scene.key_range.start(); + let mut lastkey = *settings.scene.key_range.end(); + ui.horizontal(|ui| { + ui.add(egui::DragValue::new(&mut firstkey).speed(1).range(0..=253)); + ui.add( + egui::DragValue::new(&mut lastkey) + .speed(1) + .range(firstkey + 1..=254), + ); + }); + ui.end_row(); + if firstkey != *settings.scene.key_range.start() + || lastkey != *settings.scene.key_range.end() + { + settings.scene.key_range = firstkey..=lastkey; + } + + ui.label("Note Speed: "); + ui.spacing_mut().slider_width = width / 2.0 - 100.0; + ui.add( + egui::Slider::new(&mut settings.scene.note_speed, 20.0..=0.0001) + .logarithmic(true), + ); + ui.end_row(); + }); + + ui.add_space(super::CATEG_SPACE); + ui.heading("Statistics"); + + egui::Grid::new("stats_visual_settings_grid") + .num_columns(2) + .spacing(super::SPACING) + .striped(true) + .min_col_width(width / 2.0) + .show(ui, |ui| { + ui.label("Floating:"); + ui.checkbox(&mut settings.scene.statistics.floating, ""); + ui.end_row(); + + ui.label("Border:"); + ui.checkbox(&mut settings.scene.statistics.border, ""); + ui.end_row(); + + ui.label("Background Opacity: "); + ui.spacing_mut().slider_width = width / 2.0 - 100.0; + ui.add(egui::Slider::new( + &mut settings.scene.statistics.opacity, + 0.0..=1.0, + )); + ui.end_row(); + }); + + ui.add_space(4.0); + egui::Frame::default() + .rounding(egui::Rounding::same(8.0)) + .stroke(ui.style().visuals.widgets.noninteractive.bg_stroke) + .show(ui, |ui| { + TableBuilder::new(ui) + .striped(true) + .cell_layout(egui::Layout::centered_and_justified( + egui::Direction::LeftToRight, + )) + .resizable(true) + .column(Column::exact(40.0).resizable(false)) + .column(Column::exact(width - 100.0).resizable(false)) + .column(Column::exact(60.0).resizable(false)) + .body(|mut body| { + let row_height = super::CATEG_SPACE * 3.0; + let mut temp = settings.scene.statistics.order.clone(); + for (i, item) in settings.scene.statistics.order.iter_mut().enumerate() { + body.row(row_height, |mut row| { + row.col(|ui| { + ui.checkbox(&mut temp[i].1, ""); + }); + row.col(|ui| { + ui.label(item.0.as_str()); + }); + row.col(|ui| { + ui.horizontal(|ui| { + if ui + .button(WidgetText::from("\u{2191}").text_style( + egui::TextStyle::Name("monospace big".into()), + )) + .clicked() + && i > 0 + { + temp.swap(i, i - 1); + } + + if ui + .button(WidgetText::from("\u{2193}").text_style( + egui::TextStyle::Name("monospace big".into()), + )) + .clicked() + && i < temp.len() - 1 + { + temp.swap(i, i + 1); + } + }); + }); + }); + } + settings.scene.statistics.order = temp; + }); + }); + } +} diff --git a/src/gui/window/settings_window.rs b/src/gui/window/settings_window.rs deleted file mode 100644 index f345483..0000000 --- a/src/gui/window/settings_window.rs +++ /dev/null @@ -1,186 +0,0 @@ -use egui::Context; - -use std::ops::RangeInclusive; - -use crate::{ - audio_playback::{ - xsynth::{convert_to_channel_init, convert_to_sf_init}, - AudioPlayerType, - }, - gui::window::GuiWasabiWindow, - settings::{MidiLoading, Synth, WasabiSettings}, - state::WasabiState, -}; - -pub fn draw_settings( - win: &mut GuiWasabiWindow, - settings: &mut WasabiSettings, - state: &mut WasabiState, - ctx: &Context, -) { - egui::Window::new("Settings") - .resizable(true) - .collapsible(true) - .title_bar(true) - .scroll2([false, true]) - .enabled(true) - .open(&mut state.settings_visible) - .show(ctx, |ui| { - let col_width = 160.0; - - // Synth settings section - ui.heading("Synth"); - ui.separator(); - - egui::Grid::new("synth_settings_grid") - .num_columns(2) - .spacing([40.0, 4.0]) - .min_col_width(col_width) - .show(ui, |ui| { - ui.label("Synth: "); - let synth_prev = settings.synth.synth; - egui::ComboBox::from_id_source("synth_select") - .selected_text(settings.synth.synth.as_str()) - .show_ui(ui, |ui| { - ui.selectable_value(&mut settings.synth.synth, Synth::XSynth, "XSynth"); - ui.selectable_value(&mut settings.synth.synth, Synth::Kdmapi, "KDMAPI"); - }); - if settings.synth.synth != synth_prev { - match settings.synth.synth { - Synth::Kdmapi => { - win.synth - .write() - .unwrap() - .switch_player(AudioPlayerType::Kdmapi); - } - Synth::XSynth => { - win.synth - .write() - .unwrap() - .switch_player(AudioPlayerType::XSynth { - buffer: settings.synth.buffer_ms, - use_threadpool: settings.synth.use_threadpool, - ignore_range: settings.synth.vel_ignore.clone(), - options: convert_to_channel_init(settings), - }); - win.synth.write().unwrap().set_soundfont( - &settings.synth.sfz_path, - convert_to_sf_init(settings), - ); - win.synth.write().unwrap().set_layer_count( - match settings.synth.layer_count { - 0 => None, - _ => Some(settings.synth.layer_count), - }, - ); - } - } - } - ui.end_row(); - - ui.label("Configure:"); - if ui.button("Open Synth Settings").clicked() { - state.xsynth_settings_visible = true; - } - ui.end_row(); - }); - - // MIDI settings section - ui.add_space(6.0); - ui.heading("MIDI"); - ui.separator(); - - egui::Grid::new("midi_settings_grid") - .num_columns(2) - .spacing([40.0, 4.0]) - .min_col_width(col_width) - .show(ui, |ui| { - ui.label("Note speed: "); - ui.spacing_mut().slider_width = 150.0; - ui.add(egui::Slider::new( - &mut settings.midi.note_speed, - 2.0..=0.001, - )); - ui.end_row(); - - ui.label("Random Track Colors*: "); - ui.checkbox(&mut settings.midi.random_colors, ""); - ui.end_row(); - - ui.label("Keyboard Range: "); - let mut firstkey = *settings.midi.key_range.start(); - let mut lastkey = *settings.midi.key_range.end(); - ui.horizontal(|ui| { - ui.add( - egui::DragValue::new(&mut firstkey) - .speed(1) - .clamp_range(RangeInclusive::new(0, 253)), - ); - ui.add( - egui::DragValue::new(&mut lastkey) - .speed(1) - .clamp_range(RangeInclusive::new(firstkey + 1, 254)), - ); - }); - ui.end_row(); - if firstkey != *settings.midi.key_range.start() - || lastkey != *settings.midi.key_range.end() - { - settings.midi.key_range = firstkey..=lastkey; - } - - ui.label("MIDI Loading*: "); - egui::ComboBox::from_id_source("midiload_select") - .selected_text(settings.midi.midi_loading.as_str()) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut settings.midi.midi_loading, - MidiLoading::Ram, - "In RAM", - ); - ui.selectable_value( - &mut settings.midi.midi_loading, - MidiLoading::Live, - "Live", - ); - ui.selectable_value( - &mut settings.midi.midi_loading, - MidiLoading::Cake, - "Cake", - ); - }); - }); - - // Visual settings section - ui.add_space(6.0); - ui.heading("Visual"); - ui.separator(); - - egui::Grid::new("visual_settings_grid") - .num_columns(2) - .spacing([40.0, 4.0]) - .min_col_width(col_width) - .show(ui, |ui| { - if ui.button("Toggle Fullscreen").clicked() { - state.fullscreen = true; - } - ui.end_row(); - - ui.label("Background Color: "); - ui.color_edit_button_srgba(&mut settings.visual.bg_color); - ui.end_row(); - - ui.label("Bar Color: "); - ui.color_edit_button_srgba(&mut settings.visual.bar_color); - ui.end_row(); - }); - - ui.separator(); - ui.vertical_centered(|ui| { - ui.label("Options marked with (*) will apply when a new MIDI is loaded."); - if ui.button("Save").clicked() { - settings.save_to_file(); - } - }); - }); -} diff --git a/src/gui/window/shortcuts.rs b/src/gui/window/shortcuts.rs new file mode 100644 index 0000000..0d08cd7 --- /dev/null +++ b/src/gui/window/shortcuts.rs @@ -0,0 +1,60 @@ +use crate::state::WasabiState; + +use super::GuiWasabiWindow; + +impl GuiWasabiWindow { + pub fn show_shortcuts(&mut self, ctx: &egui::Context, state: &mut WasabiState) { + let frame = + egui::Frame::inner_margin(egui::Frame::window(ctx.style().as_ref()), super::WIN_MARGIN); + let size = [400.0, 210.0]; + + egui::Window::new("Keyboard Shortcuts") + .collapsible(false) + .title_bar(true) + .scroll([false, true]) + .enabled(true) + .frame(frame) + .fixed_size(size) + .open(&mut state.show_shortcuts) + .show(ctx, |ui| { + let col_width = size[0] / 2.0; + egui::Grid::new("shortcuts_grid") + .num_columns(2) + .min_col_width(col_width) + .striped(true) + .show(ui, |ui| { + ui.label("Play / Pause Playback"); + ui.label("Space"); + ui.end_row(); + + ui.label("Skip Forward"); + ui.label("Right Arrow"); + ui.end_row(); + + ui.label("Go Back"); + ui.label("Left Arrow"); + ui.end_row(); + + ui.label("Slower Note Speed"); + ui.label("Ctrl + Up Arrow"); + ui.end_row(); + + ui.label("Faster Note Speed"); + ui.label("Ctrl + Down Arrow"); + ui.end_row(); + + ui.label("Toggle Fullscreen"); + ui.label("Alt + Enter"); + ui.end_row(); + + ui.label("Toggle Panel"); + ui.label("Ctrl + F"); + ui.end_row(); + + ui.label("Toggle Statistics"); + ui.label("Ctrl + G"); + ui.end_row(); + }); + }); + } +} diff --git a/src/gui/window/stats.rs b/src/gui/window/stats.rs index e953951..9df23ee 100644 --- a/src/gui/window/stats.rs +++ b/src/gui/window/stats.rs @@ -1,6 +1,11 @@ use egui::{Context, Frame, Pos2}; -use crate::{gui::window::GuiWasabiWindow, midi::MIDIFileBase}; +use crate::{ + gui::window::GuiWasabiWindow, + midi::{MIDIFileBase, MIDIFileStats}, + settings::{Statistics, WasabiSettings}, + utils::convert_seconds_to_time_string, +}; pub struct GuiMidiStats { time_passed: f64, @@ -28,109 +33,116 @@ impl GuiMidiStats { } } -pub fn draw_stats(win: &mut GuiWasabiWindow, ctx: &Context, pos: Pos2, mut stats: GuiMidiStats) { +fn num_or_q(num: Option) -> String { + if let Some(num) = num { + num.to_string() + } else { + "?".to_string() + } +} + +pub fn draw_stats( + win: &mut GuiWasabiWindow, + ctx: &Context, + pos: Pos2, + mut stats: GuiMidiStats, + settings: &WasabiSettings, +) { let onepx = ctx.pixels_per_point(); - let stats_frame = Frame::default() - .inner_margin(egui::style::Margin::same(7.0)) - .fill(egui::Color32::from_rgba_unmultiplied(7, 7, 7, 200)) - .stroke(egui::Stroke::new( + let opacity = settings.scene.statistics.opacity.clamp(0.0, 1.0); + let alpha = (u8::MAX as f32 * opacity).round() as u8; + + let round = 8.0; + + let mut stats_frame = Frame::default() + .inner_margin(egui::Margin::same(7.0)) + .fill(egui::Color32::from_rgba_unmultiplied(7, 7, 7, alpha)); + + if settings.scene.statistics.floating { + stats_frame = stats_frame.rounding(egui::Rounding::same(round)); + } else { + stats_frame = stats_frame.rounding(egui::Rounding { + ne: 0.0, + nw: 0.0, + sw: 0.0, + se: round, + }); + } + + if settings.scene.statistics.border { + stats_frame = stats_frame.stroke(egui::Stroke::new( onepx, egui::Color32::from_rgb(50, 50, 50), - )) - .rounding(egui::Rounding::same(6.0)); + )); + } egui::Window::new("Stats") .resizable(false) .collapsible(false) .title_bar(false) - .scroll2([false, false]) + .scroll([false, false]) .enabled(true) .frame(stats_frame) .fixed_pos(pos) .fixed_size(egui::Vec2::new(200.0, 128.0)) .show(ctx, |ui| { - let mut time_millis: u64 = 0; - let mut time_sec: u64 = 0; - let mut time_min: u64 = 0; - let mut length_millis: u64 = 0; - let mut length_sec: u64 = 0; - let mut length_min: u64 = 0; - - let mut note_stats = Default::default(); + ui.spacing_mut().interact_size.y = 16.0; + let mut note_stats = MIDIFileStats::default(); if let Some(midi_file) = win.midi_file.as_mut() { stats.time_total = midi_file.midi_length().unwrap_or(0.0); let time = midi_file.timer().get_time().as_secs_f64(); - length_millis = (stats.time_total * 10.0) as u64 % 10; - length_sec = stats.time_total as u64 % 60; - length_min = stats.time_total as u64 / 60; - if time > stats.time_total { stats.time_passed = stats.time_total; } else { stats.time_passed = time; } - time_millis = (stats.time_passed * 10.0) as u64 % 10; - time_sec = stats.time_passed as u64 % 60; - time_min = stats.time_passed as u64 / 60; - note_stats = midi_file.stats(); } - ui.horizontal(|ui| { - ui.monospace("Time:"); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.monospace(format!( - "{:0width$}:{:0width$}.{} / {:0width$}:{:0width$}.{}", - time_min, - time_sec, - time_millis, - length_min, - length_sec, - length_millis, - width = 2 - )); - }); - }); - - ui.horizontal(|ui| { - ui.monospace("FPS:"); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.monospace(format!("{}", win.fps.get_fps().round())); - }); - }); - - ui.horizontal(|ui| { - ui.monospace("Voice Count:"); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.monospace(format!("{}", stats.voice_count)); - }); - }); - - ui.horizontal(|ui| { - ui.monospace("Rendered:"); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.monospace(format!("{}", stats.notes_on_screen)); - }); - }); - - fn num_or_q(num: Option) -> String { - if let Some(num) = num { - num.to_string() - } else { - "?".to_string() - } + for i in settings.scene.statistics.order.iter().filter(|i| i.1) { + match i.0 { + Statistics::Time => ui.horizontal(|ui| { + ui.monospace("Time:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.monospace(format!( + "{} / {}", + convert_seconds_to_time_string(stats.time_passed), + convert_seconds_to_time_string(stats.time_total) + )); + }); + }), + Statistics::Fps => ui.horizontal(|ui| { + ui.monospace("FPS:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.monospace(format!("{}", win.fps.get_fps().round())); + }); + }), + Statistics::VoiceCount => ui.horizontal(|ui| { + ui.monospace("Voice Count:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.monospace(format!("{}", stats.voice_count)); + }); + }), + Statistics::Rendered => ui.horizontal(|ui| { + ui.monospace("Rendered:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.monospace(format!("{}", stats.notes_on_screen)); + }); + }), + Statistics::NoteCount => { + ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| { + ui.monospace(format!( + "{} / {}", + num_or_q(note_stats.passed_notes), + num_or_q(note_stats.total_notes) + )); + }) + } + }; } - - ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| { - ui.monospace(format!( - "{} / {}", - num_or_q(note_stats.passed_notes), - num_or_q(note_stats.total_notes) - )); - }); }); } diff --git a/src/gui/window/top_panel.rs b/src/gui/window/top_panel.rs deleted file mode 100644 index bfae1e7..0000000 --- a/src/gui/window/top_panel.rs +++ /dev/null @@ -1,90 +0,0 @@ -use egui::{Context, Frame}; - -use std::time::Duration; - -use crate::{ - gui::window::GuiWasabiWindow, midi::MIDIFileBase, settings::WasabiSettings, state::WasabiState, -}; - -pub fn draw_panel( - win: &mut GuiWasabiWindow, - settings: &mut WasabiSettings, - state: &mut WasabiState, - ctx: &Context, -) { - let panel_frame = Frame::default() - .inner_margin(egui::style::Margin::same(10.0)) - .fill(egui::Color32::from_rgb(42, 42, 42)); - - egui::TopBottomPanel::top("Top panel") - .frame(panel_frame) - .show_separator_line(false) - .show(ctx, |ui| { - ui.horizontal(|ui| { - if ui.button("Open").clicked() { - win.open_midi_dialog(settings, state); - } - - if let Some(midi_file) = win.midi_file.as_mut() { - if ui.button("Unload").clicked() { - midi_file.timer_mut().pause(); - win.synth.write().unwrap().reset(); - win.midi_file = None; - } - } - - ui.add_space(10.0); - - if ui.button("Settings").clicked() { - match state.settings_visible { - true => state.settings_visible = false, - false => state.settings_visible = true, - } - } - - ui.add_space(10.0); - - if ui.button("Play").clicked() { - if let Some(midi_file) = win.midi_file.as_mut() { - midi_file.timer_mut().play(); - } - } - if ui.button("Pause").clicked() { - if let Some(midi_file) = win.midi_file.as_mut() { - midi_file.timer_mut().pause(); - } - } - - ui.add_space(10.0); - - ui.horizontal(|ui| { - ui.label("Note speed: "); - ui.add( - egui::Slider::new(&mut settings.midi.note_speed, 2.0..=0.001) - .show_value(false), - ); - }) - }); - - ui.spacing_mut().slider_width = ctx.available_rect().width() - 20.0; - let mut empty_slider = - || ui.add(egui::Slider::new(&mut 0.0, 0.0..=1.0).show_value(false)); - if let Some(midi_file) = win.midi_file.as_mut() { - if let Some(length) = midi_file.midi_length() { - let mut time = midi_file.timer().get_time().as_secs_f64(); - let time_prev = time; - - ui.add(egui::Slider::new(&mut time, 0.0..=length).show_value(false)); - if (time_prev != time) - && (midi_file.allows_seeking_backward() || time_prev < time) - { - midi_file.timer_mut().seek(Duration::from_secs_f64(time)); - } - } else { - empty_slider(); - } - } else { - empty_slider(); - } - }); -} diff --git a/src/gui/window/xsynth_settings.rs b/src/gui/window/xsynth_settings.rs deleted file mode 100644 index 7a12399..0000000 --- a/src/gui/window/xsynth_settings.rs +++ /dev/null @@ -1,174 +0,0 @@ -use egui::Context; - -use std::ops::RangeInclusive; - -use crate::{ - audio_playback::{ - xsynth::{convert_to_channel_init, convert_to_sf_init}, - AudioPlayerType, - }, - gui::window::GuiWasabiWindow, - settings::WasabiSettings, - state::WasabiState, -}; - -pub fn draw_xsynth_settings( - win: &mut GuiWasabiWindow, - settings: &mut WasabiSettings, - state: &mut WasabiState, - ctx: &Context, -) { - egui::Window::new("XSynth Settings") - .resizable(true) - .collapsible(true) - .title_bar(true) - .scroll2([false, true]) - .enabled(true) - .open(&mut state.xsynth_settings_visible) - .show(ctx, |ui| { - let col_width = 240.0; - - ui.heading("Synth"); - ui.separator(); - - egui::Grid::new("synth_settings_grid") - .num_columns(2) - .spacing([40.0, 4.0]) - .min_col_width(col_width) - .show(ui, |ui| { - ui.label("Synth Render Buffer (ms)*: "); - ui.add( - egui::DragValue::new(&mut settings.synth.buffer_ms) - .speed(0.1) - .clamp_range(RangeInclusive::new(0.001, 1000.0)), - ); - ui.end_row(); - - ui.label("SoundFont Path: "); - ui.horizontal(|ui| { - ui.add(egui::TextEdit::singleline(&mut settings.synth.sfz_path)); - - if ui.button("Browse...").clicked() { - // If windows, just use the native dialog - let sfz_path = rfd::FileDialog::new() - .add_filter("Supported SoundFonts", &["sfz", "sf2", "SF2"]) - .pick_file(); - - if let Some(sfz_path) = sfz_path { - if let Ok(path) = sfz_path.into_os_string().into_string() { - settings.synth.sfz_path = path; - } - } - } - - if ui.button("Load").clicked() { - win.synth.write().unwrap().set_soundfont( - &settings.synth.sfz_path, - convert_to_sf_init(settings), - ); - } - }); - ui.end_row(); - - ui.label("Limit Layers: "); - let layer_limit_prev = settings.synth.limit_layers; - ui.checkbox(&mut settings.synth.limit_layers, ""); - ui.end_row(); - - ui.label("Synth Layer Count: "); - let layer_count_prev = settings.synth.layer_count; - ui.add_enabled_ui(settings.synth.limit_layers, |ui| { - ui.add( - egui::DragValue::new(&mut settings.synth.layer_count) - .speed(1) - .clamp_range(RangeInclusive::new(1, 200)), - ); - }); - if settings.synth.layer_count != layer_count_prev - || layer_limit_prev != settings.synth.limit_layers - { - win.synth.write().unwrap().set_layer_count( - if settings.synth.limit_layers { - Some(settings.synth.layer_count) - } else { - None - }, - ); - } - ui.end_row(); - - ui.label("Ignore notes with velocities between*: "); - let mut lovel = *settings.synth.vel_ignore.start(); - let mut hivel = *settings.synth.vel_ignore.end(); - ui.horizontal(|ui| { - ui.add( - egui::DragValue::new(&mut lovel) - .speed(1) - .clamp_range(RangeInclusive::new(0, 127)), - ); - ui.label("and"); - ui.add( - egui::DragValue::new(&mut hivel) - .speed(1) - .clamp_range(RangeInclusive::new(lovel, 127)), - ); - }); - ui.end_row(); - if lovel != *settings.synth.vel_ignore.start() - || hivel != *settings.synth.vel_ignore.end() - { - settings.synth.vel_ignore = lovel..=hivel; - } - }); - - ui.add_space(6.0); - ui.heading("Engine"); - ui.separator(); - - egui::Grid::new("engine_settings_grid") - .num_columns(2) - .spacing([40.0, 4.0]) - .min_col_width(col_width) - .show(ui, |ui| { - ui.label("Fade out voice when killing it*: "); - ui.checkbox(&mut settings.synth.fade_out_kill, ""); - ui.end_row(); - - ui.label("Use Effects*: "); - ui.checkbox(&mut settings.synth.use_effects, ""); - ui.end_row(); - - ui.label("Use Threadpool*: "); - ui.checkbox(&mut settings.synth.use_threadpool, ""); - ui.end_row(); - }); - - ui.separator(); - ui.vertical_centered(|ui| { - ui.label("Options marked with (*) will apply when the synth is reloaded."); - if ui.button("Reload XSynth").clicked() { - win.synth - .write() - .unwrap() - .switch_player(AudioPlayerType::XSynth { - buffer: settings.synth.buffer_ms, - use_threadpool: settings.synth.use_threadpool, - ignore_range: settings.synth.vel_ignore.clone(), - options: convert_to_channel_init(settings), - }); - win.synth - .write() - .unwrap() - .set_soundfont(&settings.synth.sfz_path, convert_to_sf_init(settings)); - win.synth - .write() - .unwrap() - .set_layer_count(if settings.synth.limit_layers { - Some(settings.synth.layer_count) - } else { - None - }); - } - }); - }); -} diff --git a/src/main.rs b/src/main.rs index 4f0e8ea..a3c27b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,13 +16,14 @@ use gui::{window::GuiWasabiWindow, GuiRenderer, GuiState}; use renderer::Renderer; use vulkano::swapchain::PresentMode; -use settings::WasabiSettings; -use state::WasabiState; -use winit::{ +use egui_winit::winit::{ dpi::{LogicalSize, Size}, event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, + event_loop::EventLoop, }; +use settings::WasabiSettings; +use state::WasabiState; +use winit::event_loop::ControlFlow; pub const WINDOW_SIZE: Size = Size::Logical(LogicalSize { width: 1280.0, @@ -34,34 +35,25 @@ pub const WAYLAND_PRESENT_MODE: PresentMode = PresentMode::Mailbox; pub fn main() { // Winit event loop - let event_loop = EventLoop::new(); - let monitor = event_loop - .available_monitors() - .next() - .expect("no monitor found!"); - - let mode = monitor.video_modes().next().expect("no mode found"); + let event_loop = EventLoop::new().unwrap(); + event_loop.set_control_flow(ControlFlow::Poll); // Load the settings values let mut settings = WasabiSettings::new_or_load(); + settings.save_to_file(); let mut wasabi_state = WasabiState::default(); // Create renderer for our scene & ui - let mut renderer = Renderer::new( - &event_loop, - "Wasabi", - settings.visual.fullscreen, - mode.clone(), - ); + let mut renderer = Renderer::new(&event_loop, "Wasabi"); // Vulkano & Winit & egui integration let mut gui = Gui::new( &event_loop, renderer.surface(), renderer.queue(), + renderer.format(), GuiConfig { is_overlay: true, - preferred_format: Some(renderer.format()), ..Default::default() }, ); @@ -75,62 +67,75 @@ pub fn main() { let mut gui_state = GuiWasabiWindow::new(&mut gui_render_data, &mut settings); - event_loop.run(move |event, _, control_flow| { - let device = renderer.device(); - let queue = renderer.queue(); - let format = renderer.format(); - - // Update Egui integration so the UI works! - match event { - Event::WindowEvent { event, window_id } if window_id == renderer.window().id() => { - let _pass_events_to_game = !gui.update(&event); - match event { - WindowEvent::Resized(size) => { - renderer.resize(Some(size)); + event_loop + .run(move |event, target| { + let device = renderer.device(); + let queue = renderer.queue(); + let format = renderer.format(); + + // Update Egui integration so the UI works! + match event { + Event::WindowEvent { event, window_id } if window_id == renderer.window().id() => { + let _pass_events_to_game = !gui.update(&event); + match event { + WindowEvent::Resized(size) => { + renderer.resize(Some(size)); + } + WindowEvent::ScaleFactorChanged { .. } => { + renderer.resize(None); + } + WindowEvent::CloseRequested => { + target.exit(); + } + WindowEvent::DroppedFile(path) => { + gui_state.load_midi(&mut settings, path); + } + WindowEvent::RedrawRequested => { + renderer.render(|frame, future| { + // Generate egui layouts + gui.immediate_ui(|gui| { + let mut gui_render_data = GuiRenderer { + gui, + device, + queue, + format, + }; + + let mut state = GuiState { + renderer: &mut gui_render_data, + frame, + }; + egui_extras::install_image_loaders( + &state.renderer.gui.context(), + ); + gui_state.layout(&mut state, &mut settings, &mut wasabi_state); + }); + + // Render the layouts + gui.draw_on_image(future, frame.image.clone()) + }); + } + _ => (), } - WindowEvent::ScaleFactorChanged { .. } => { - renderer.resize(None); - } - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - WindowEvent::DroppedFile(path) => { - gui_state.load_midi(&mut settings, path); - } - _ => (), } + Event::NewEvents(..) => { + renderer.window().request_redraw(); + } + _ => (), } - Event::RedrawRequested(_) => { - renderer.render(|frame, future| { - // Generate egui layouts - gui.immediate_ui(|gui| { - let mut gui_render_data = GuiRenderer { - gui, - device, - queue, - format, - }; - - let mut state = GuiState { - renderer: &mut gui_render_data, - frame, - }; - gui_state.layout(&mut state, &mut settings, &mut wasabi_state); - }); - - // Render the layouts - gui.draw_on_image(future, frame.image.clone()) - }); - } - Event::MainEventsCleared => { - renderer.window().request_redraw(); + + let mode = target + .available_monitors() + .next() + .unwrap() + .video_modes() + .next() + .unwrap(); + + if wasabi_state.fullscreen { + renderer.set_fullscreen(mode); + wasabi_state.fullscreen = false; } - _ => (), - } - - if wasabi_state.fullscreen { - renderer.set_fullscreen(mode.clone()); - wasabi_state.fullscreen = false; - } - }); + }) + .unwrap(); } diff --git a/src/midi/audio/live.rs b/src/midi/audio/live.rs index 348d922..452062f 100644 --- a/src/midi/audio/live.rs +++ b/src/midi/audio/live.rs @@ -7,7 +7,7 @@ use std::{ use crossbeam_channel::Receiver; use crate::{ - audio_playback::SimpleTemporaryPlayer, + audio_playback::WasabiAudioPlayer, midi::shared::{ audio::CompressedAudio, timer::{TimeListener, UnpauseWaitResult, WaitResult}, @@ -17,14 +17,14 @@ use crate::{ pub struct LiveAudioPlayer { events: Receiver, timer: TimeListener, - player: Arc>, + player: Arc>, } impl LiveAudioPlayer { pub fn new( events: Receiver, timer: TimeListener, - player: Arc>, + player: Arc>, ) -> Self { LiveAudioPlayer { events, diff --git a/src/midi/audio/ram.rs b/src/midi/audio/ram.rs index a6d47a6..0db02b2 100644 --- a/src/midi/audio/ram.rs +++ b/src/midi/audio/ram.rs @@ -5,7 +5,7 @@ use std::{ }; use crate::{ - audio_playback::SimpleTemporaryPlayer, + audio_playback::WasabiAudioPlayer, midi::shared::{ audio::CompressedAudio, timer::{SeekWaitResult, TimeListener, UnpauseWaitResult, WaitResult}, @@ -15,7 +15,7 @@ use crate::{ pub struct InRamAudioPlayer { events: Vec, timer: TimeListener, - player: Arc>, + player: Arc>, index: usize, } @@ -23,7 +23,7 @@ impl InRamAudioPlayer { pub fn new( events: Vec, timer: TimeListener, - player: Arc>, + player: Arc>, ) -> Self { InRamAudioPlayer { events, diff --git a/src/midi/cake/mod.rs b/src/midi/cake/mod.rs index 0eee50d..4196c3d 100644 --- a/src/midi/cake/mod.rs +++ b/src/midi/cake/mod.rs @@ -15,7 +15,7 @@ use midi_toolkit::{ }; use crate::{ - audio_playback::SimpleTemporaryPlayer, + audio_playback::WasabiAudioPlayer, midi::{ audio::ram::InRamAudioPlayer, cake::tree_threader::{NoteEvent, ThreadedTreeSerializers}, @@ -23,6 +23,7 @@ use crate::{ shared::{audio::CompressedAudio, timer::TimeKeeper}, MIDIColor, }, + settings::WasabiSettings, }; use self::blocks::CakeBlock; @@ -47,8 +48,8 @@ pub struct CakeMIDIFile { impl CakeMIDIFile { pub fn load_from_file( path: &str, - player: Arc>, - random_colors: bool, + player: Arc>, + settings: &WasabiSettings, ) -> Self { let ticks_per_second = 10000; @@ -64,12 +65,7 @@ impl CakeMIDIFile { |>unwrap_items() ); - let track_count = midi.track_count(); - let colors = if random_colors { - MIDIColor::new_random_vec_for_tracks(track_count) - } else { - MIDIColor::new_vec_for_tracks(track_count) - }; + let colors = MIDIColor::new_vec_from_settings(midi.track_count(), settings); type Ev = Delta>>; let (key_snd, key_rcv) = crossbeam_channel::bounded::>(1000); diff --git a/src/midi/live/mod.rs b/src/midi/live/mod.rs index 7d4e4e8..3ce23c6 100644 --- a/src/midi/live/mod.rs +++ b/src/midi/live/mod.rs @@ -5,7 +5,7 @@ use std::{ use midi_toolkit::{io::MIDIFile as TKMIDIFile, sequence::event::get_channels_array_statistics}; -use crate::audio_playback::SimpleTemporaryPlayer; +use crate::{audio_playback::WasabiAudioPlayer, settings::WasabiSettings}; use self::{ parse::LiveMidiParser, @@ -13,8 +13,8 @@ use self::{ }; use super::{ - open_file_and_signature, shared::timer::TimeKeeper, MIDIFile, MIDIFileBase, MIDIFileStats, - MIDIFileUniqueSignature, MIDIViewRange, + open_file_and_signature, shared::timer::TimeKeeper, MIDIColor, MIDIFile, MIDIFileBase, + MIDIFileStats, MIDIFileUniqueSignature, MIDIViewRange, }; pub mod block; @@ -37,8 +37,8 @@ pub struct LiveLoadMIDIFile { impl LiveLoadMIDIFile { pub fn load_from_file( path: &str, - player: Arc>, - random_colors: bool, + player: Arc>, + settings: &WasabiSettings, ) -> Self { let (file, signature) = open_file_and_signature(path); @@ -62,8 +62,10 @@ impl LiveLoadMIDIFile { let mut timer = TimeKeeper::new(); - let parer = LiveMidiParser::init(&midi, player, &mut timer); - let file = LiveNoteViewData::new(parer, midi.track_count(), random_colors); + let colors = MIDIColor::new_vec_from_settings(midi.track_count(), settings); + + let parser = LiveMidiParser::init(&midi, player, &mut timer); + let file = LiveNoteViewData::new(parser, colors); LiveLoadMIDIFile { view_data: file, diff --git a/src/midi/live/parse.rs b/src/midi/live/parse.rs index c1b3142..5e0a6e6 100644 --- a/src/midi/live/parse.rs +++ b/src/midi/live/parse.rs @@ -17,7 +17,7 @@ use midi_toolkit::{ }; use crate::{ - audio_playback::SimpleTemporaryPlayer, + audio_playback::WasabiAudioPlayer, midi::{ audio::live::LiveAudioPlayer, shared::timer::{TimeKeeper, WaitResult}, @@ -46,7 +46,7 @@ pub struct LiveMidiParser { impl LiveMidiParser { pub fn init( midi: &TKMIDIFile, - player: Arc>, + player: Arc>, timer: &mut TimeKeeper, ) -> Self { let ppq = midi.ppq(); diff --git a/src/midi/live/view.rs b/src/midi/live/view.rs index a28b259..1faa022 100644 --- a/src/midi/live/view.rs +++ b/src/midi/live/view.rs @@ -25,7 +25,7 @@ impl<'a> LiveCurrentNoteViews<'a> { } impl LiveNoteViewData { - pub fn new(parser: LiveMidiParser, track_count: usize, random_colors: bool) -> Self { + pub fn new(parser: LiveMidiParser, colors: Vec) -> Self { let mut columns = Vec::with_capacity(256); columns.resize_with(256, LiveNoteColumn::new); LiveNoteViewData { @@ -35,11 +35,7 @@ impl LiveNoteViewData { start: 0.0, end: 0.0, }, - default_track_colors: if random_colors { - MIDIColor::new_random_vec_for_tracks(track_count) - } else { - MIDIColor::new_vec_for_tracks(track_count) - }, + default_track_colors: colors, } } diff --git a/src/midi/mod.rs b/src/midi/mod.rs index 1d72ffd..3d1d64d 100644 --- a/src/midi/mod.rs +++ b/src/midi/mod.rs @@ -8,7 +8,7 @@ mod ram; mod audio; mod shared; -use std::{fs::File, time::UNIX_EPOCH}; +use std::{fs::File, path::PathBuf, time::UNIX_EPOCH}; use enum_dispatch::enum_dispatch; use palette::{convert::FromColorUnclamped, Hsv, Srgb}; @@ -18,6 +18,8 @@ pub use cake::{blocks::CakeBlock, intvec4::IntVector4, CakeMIDIFile, CakeSignatu pub use live::LiveLoadMIDIFile; pub use ram::InRamMIDIFile; +use crate::settings::{Colors, WasabiSettings}; + use self::shared::timer::TimeKeeper; #[derive(Debug, Clone, Copy, Default)] @@ -90,7 +92,7 @@ impl MIDIColor { ) } - pub fn new_vec_for_tracks(tracks: usize) -> Vec { + pub fn new_vec(tracks: usize) -> Vec { let count = tracks * 16; let mut vec = Vec::with_capacity(count); @@ -104,7 +106,7 @@ impl MIDIColor { vec } - pub fn new_random_vec_for_tracks(tracks: usize) -> Vec { + pub fn new_random_vec(tracks: usize) -> Vec { let count = tracks * 16; let mut vec = Vec::with_capacity(count); @@ -118,6 +120,25 @@ impl MIDIColor { vec } + pub fn new_vec_from_palette(tracks: usize, path: impl Into) -> Vec { + let path: PathBuf = path.into(); + if path.exists() { + return Vec::new(); + } + + Self::new_vec(tracks) + } + + pub fn new_vec_from_settings(tracks: usize, settings: &WasabiSettings) -> Vec { + match settings.midi.colors { + Colors::Rainbow => MIDIColor::new_vec(tracks), + Colors::Random => MIDIColor::new_random_vec(tracks), + Colors::Palette => { + MIDIColor::new_vec_from_palette(tracks, settings.midi.palette_path.clone()) + } + } + } + pub fn as_u32(&self) -> u32 { self.0 } diff --git a/src/midi/ram/parse.rs b/src/midi/ram/parse.rs index e318334..9f86b54 100644 --- a/src/midi/ram/parse.rs +++ b/src/midi/ram/parse.rs @@ -16,13 +16,15 @@ use midi_toolkit::{ use rustc_hash::FxHashMap; use crate::{ - audio_playback::SimpleTemporaryPlayer, + audio_playback::WasabiAudioPlayer, midi::{ audio::ram::InRamAudioPlayer, open_file_and_signature, ram::{column::InRamNoteColumn, view::InRamNoteViewData}, shared::{audio::CompressedAudio, timer::TimeKeeper, track_channel::TrackAndChannel}, + MIDIColor, }, + settings::WasabiSettings, }; use super::{block::InRamNoteBlock, InRamMIDIFile}; @@ -98,8 +100,8 @@ impl Key { impl InRamMIDIFile { pub fn load_from_file( path: &str, - player: Arc>, - random_colors: bool, + player: Arc>, + settings: &WasabiSettings, ) -> Self { let (file, signature) = open_file_and_signature(path); let midi = TKMIDIFile::open_from_stream(file, None).unwrap(); @@ -192,8 +194,10 @@ impl InRamMIDIFile { .map(|key| InRamNoteColumn::new(key.column)) .collect(); + let colors = MIDIColor::new_vec_from_settings(midi.track_count(), settings); + InRamMIDIFile { - view_data: InRamNoteViewData::new(columns, midi.track_count(), random_colors), + view_data: InRamNoteViewData::new(columns, colors), timer, length, note_count, diff --git a/src/midi/ram/view.rs b/src/midi/ram/view.rs index dbbeaa4..fce8085 100644 --- a/src/midi/ram/view.rs +++ b/src/midi/ram/view.rs @@ -22,18 +22,14 @@ impl<'a> InRamCurrentNoteViews<'a> { } impl InRamNoteViewData { - pub fn new(columns: Vec, track_count: usize, random_colors: bool) -> Self { + pub fn new(columns: Vec, colors: Vec) -> Self { InRamNoteViewData { columns, view_range: MIDIViewRange { start: 0.0, end: 0.0, }, - default_track_colors: if random_colors { - MIDIColor::new_random_vec_for_tracks(track_count) - } else { - MIDIColor::new_vec_for_tracks(track_count) - }, + default_track_colors: colors, } } diff --git a/src/renderer.rs b/src/renderer.rs index 8e54a55..b18ae72 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -2,6 +2,7 @@ pub mod swapchain; use std::sync::Arc; +use raw_window_handle::RawDisplayHandle; use vulkano::{ device::{ physical::PhysicalDeviceType, Device, DeviceCreateInfo, DeviceExtensions, Features, Queue, @@ -14,16 +15,12 @@ use vulkano::{ Version, VulkanLibrary, }; -use vulkano_win::create_surface_from_winit; -#[cfg(target_os = "linux")] -use winit::platform::wayland::EventLoopWindowTargetExtWayland; -#[cfg(target_os = "linux")] -use winit::platform::wayland::WindowExtWayland; +use raw_window_handle::HasDisplayHandle; use winit::{ dpi::PhysicalSize, event_loop::EventLoop, - monitor::VideoMode, - window::{Fullscreen, Icon, Window, WindowBuilder}, + monitor::VideoModeHandle, + window::{Fullscreen, Icon, Window, WindowAttributes}, }; use self::swapchain::{ManagedSwapchain, SwapchainFrame}; @@ -40,13 +37,13 @@ pub struct Renderer { } impl Renderer { - pub fn new(event_loop: &EventLoop<()>, name: &str, fullscreen: bool, mode: VideoMode) -> Self { + pub fn new(event_loop: &EventLoop<()>, name: &str) -> Self { // Why let library = VulkanLibrary::new().unwrap(); // Add instance extensions based on needs let instance_extensions = InstanceExtensions { - ..vulkano_win::required_extensions(&library) + ..Surface::required_extensions(event_loop).unwrap() }; // Create instance @@ -61,30 +58,17 @@ impl Renderer { .expect("Failed to create instance"); // Create rendering surface along with window - let window = WindowBuilder::new() + let win_attr = WindowAttributes::default() .with_window_icon(Some(Icon::from_rgba(ICON.to_vec(), 16, 16).unwrap())) - .with_fullscreen({ - if fullscreen { - #[cfg(target_os = "linux")] - let fullscreen = if event_loop.is_wayland() { - Some(Fullscreen::Borderless(None)) - } else { - Some(Fullscreen::Exclusive(mode)) - }; - #[cfg(not(target_os = "linux"))] - let fullscreen = Some(Fullscreen::Exclusive(mode)); - fullscreen - } else { - None - } - }) .with_inner_size(crate::WINDOW_SIZE) - .with_title(name) - .build(event_loop) + .with_title(name); + let window = event_loop + .create_window(win_attr) .expect("Failed to create vulkan surface & window"); + let window = Arc::new(window); - let surface = create_surface_from_winit(window.clone(), instance.clone()) + let surface = Surface::from_window(instance.clone(), window.clone()) .expect("Failed to create surface"); // Get most performant physical device (device with most memory) @@ -149,7 +133,10 @@ impl Renderer { physical_device, device.clone(), #[cfg(target_os = "linux")] - if event_loop.is_wayland() { + if matches!( + event_loop.display_handle().unwrap().as_raw(), + RawDisplayHandle::Wayland(..) + ) { println!("Present Mode: {:?}", crate::WAYLAND_PRESENT_MODE); crate::WAYLAND_PRESENT_MODE } else { @@ -196,17 +183,16 @@ impl Renderer { self.swap_chain.resize(size); } - pub fn set_fullscreen(&self, mode: VideoMode) { + pub fn set_fullscreen(&self, mode: VideoModeHandle) { if self.window.fullscreen().is_none() { - #[cfg(target_os = "linux")] - let fullscreen = if self.window.wayland_display().is_some() { + let fullscreen = if matches!( + self.window.display_handle().unwrap().as_raw(), + RawDisplayHandle::Wayland(..) + ) { Some(Fullscreen::Borderless(None)) } else { Some(Fullscreen::Exclusive(mode)) }; - #[cfg(not(target_os = "linux"))] - let fullscreen = Some(Fullscreen::Exclusive(mode)); - self.window.set_fullscreen(fullscreen); } else { self.window.set_fullscreen(None); diff --git a/src/renderer/swapchain.rs b/src/renderer/swapchain.rs index 9aea4a2..b28b8db 100644 --- a/src/renderer/swapchain.rs +++ b/src/renderer/swapchain.rs @@ -1,16 +1,17 @@ use std::sync::Arc; +use egui_winit::winit::{dpi::PhysicalSize, window::Window}; use vulkano::{ device::{physical::PhysicalDevice, Device, Queue}, format::Format, - image::{view::ImageView, ImageUsage, SwapchainImage}, + image::{view::ImageView, ImageUsage}, swapchain::{ - AcquireError, PresentMode, Surface, Swapchain, SwapchainAcquireFuture, SwapchainCreateInfo, - SwapchainCreationError, SwapchainPresentInfo, + PresentMode, Surface, Swapchain, SwapchainAcquireFuture, SwapchainCreateInfo, + SwapchainPresentInfo, }, - sync::{self, FlushError, GpuFuture}, + sync::{self, GpuFuture}, + Validated, VulkanError, }; -use winit::{dpi::PhysicalSize, window::Window}; #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub struct ImagesState { @@ -26,7 +27,7 @@ pub struct SwapchainState { pub struct ManagedSwapchain { state: SwapchainState, swap_chain: Arc, - image_views: Vec>>, + image_views: Vec>, previous_frame_end: Option>, device: Arc, recreate_on_next_frame: bool, @@ -49,8 +50,9 @@ impl ManagedSwapchain { .unwrap(); let image_format = formats .iter() - .find(|v| v.0 == Format::B8G8R8A8_SRGB) - .map(|v| v.0); + .find(|v| v.0 == Format::B8G8R8A8_UNORM) + .map(|v| v.0) + .unwrap_or(Format::B8G8R8A8_UNORM); let image_extent = window.inner_size().into(); let (swapchain, images) = Swapchain::new( @@ -110,7 +112,7 @@ impl ManagedSwapchain { ..self.swap_chain.create_info() }) { Ok(r) => r, - Err(SwapchainCreationError::ImageExtentNotSupported { .. }) => return, + Err(Validated::Error { .. }) => return, Err(e) => panic!("Failed to recreate swapchain: {e:?}"), }; self.swap_chain = new_swapchain; @@ -144,11 +146,15 @@ impl ManagedSwapchain { let (image_num, suboptimal, acquire_future) = match next { Ok(r) => r, // TODO: Handle more errors, e.g. DeviceLost, by re-creating the entire graphics chain - Err(AcquireError::OutOfDate) => { - self.recreate(); - continue; + Err(Validated::Error(e)) => { + if e == VulkanError::OutOfDate { + self.recreate(); + continue; + } else { + panic!("Failed to acquire next image: {e:?}"); + } } - Err(e) => panic!("Failed to acquire next image: {e:?}"), + Err(e) => panic!("Unknown error: {e:?}"), }; if suboptimal { @@ -172,7 +178,7 @@ pub struct SwapchainFrame<'a> { presented: bool, pub image_num: u32, - pub image: Arc>, + pub image: Arc, managed_swap_chain: &'a mut ManagedSwapchain, } @@ -200,12 +206,17 @@ impl<'a> SwapchainFrame<'a> { } sc.previous_frame_end = Some(future.boxed()); } - Err(FlushError::OutOfDate) => { - sc.recreate_on_next_frame = true; - sc.previous_frame_end = Some(sync::now(sc.device.clone()).boxed()); + Err(Validated::Error(e)) => { + if e == VulkanError::OutOfDate { + sc.recreate_on_next_frame = true; + sc.previous_frame_end = Some(sync::now(sc.device.clone()).boxed()); + } else { + println!("Failed to flush future: {e:?}"); + sc.previous_frame_end = Some(sync::now(sc.device.clone()).boxed()); + } } Err(e) => { - println!("Failed to flush future: {e:?}"); + println!("Unknown error: {e:?}"); sc.previous_frame_end = Some(sync::now(sc.device.clone()).boxed()); } } diff --git a/src/scenes.rs b/src/scenes.rs index 283b238..4d348b5 100644 --- a/src/scenes.rs +++ b/src/scenes.rs @@ -2,14 +2,14 @@ use std::sync::Arc; use vulkano::{ device::Device, - image::{view::ImageView, AttachmentImage}, + image::{view::ImageView, Image, ImageCreateInfo, ImageUsage}, memory::allocator::StandardMemoryAllocator, }; use crate::{gui::GuiState, renderer::swapchain::ImagesState}; pub struct SceneImage { - pub image: Arc>, + pub image: Arc, pub id: egui::TextureId, } @@ -39,17 +39,24 @@ impl SceneSwapchain { state.renderer.gui.unregister_user_image(image.id); } - let allocator = StandardMemoryAllocator::new_default(self.device.clone()); + let allocator = Arc::new(StandardMemoryAllocator::new_default(self.device.clone())); + let mut create_info: ImageCreateInfo = Default::default(); + create_info.format = image_state.format; + create_info.extent = [size[0], size[1], 1]; + create_info.usage = ImageUsage::SAMPLED; // Create new images for _ in 0..image_state.count { let image = ImageView::new_default( - AttachmentImage::sampled_input_attachment(&allocator, size, image_state.format) + Image::new(allocator.clone(), create_info.clone(), Default::default()) .expect("Failed to create scene image"), ) .expect("Failed to create scene image view"); - let id = state.renderer.gui.register_user_image_view(image.clone()); + let id = state + .renderer + .gui + .register_user_image_view(image.clone(), Default::default()); self.scene_images.push(SceneImage { image, id }); } diff --git a/src/settings/enums.rs b/src/settings/enums.rs new file mode 100644 index 0000000..f741d4d --- /dev/null +++ b/src/settings/enums.rs @@ -0,0 +1,167 @@ +use num_enum::FromPrimitive; +use serde_derive::{Deserialize, Serialize}; +use std::{fmt::Debug, slice::Iter, str::FromStr}; + +#[repr(usize)] +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, FromPrimitive)] +#[serde(rename_all = "lowercase")] +pub enum MidiParsing { + #[default] + Ram = 0, + Live = 1, + Cake = 2, +} + +impl MidiParsing { + pub const fn as_str(self) -> &'static str { + match self { + MidiParsing::Ram => "Standard (RAM)", + MidiParsing::Live => "Standard (Live)", + MidiParsing::Cake => "Cake", + } + } +} + +impl FromStr for MidiParsing { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "ram" => Ok(MidiParsing::Ram), + "live" => Ok(MidiParsing::Live), + "cake" => Ok(MidiParsing::Cake), + s => Err(format!( + "{} was not expected. Expected one of `ram`, `live` or `cake`", + s + )), + } + } +} + +#[repr(usize)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, FromPrimitive)] +#[serde(rename_all = "lowercase")] +pub enum Synth { + #[default] + XSynth = 0, + Kdmapi = 1, + MidiDevice = 2, + None = 3, +} + +impl Synth { + #[inline] + pub const fn as_str(self) -> &'static str { + match self { + Synth::XSynth => "Built-In (XSynth)", + Synth::Kdmapi => "KDMAPI", + Synth::MidiDevice => "MIDI Device", + Synth::None => "None", + } + } +} + +impl FromStr for Synth { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "xsynth" => Ok(Synth::XSynth), + "kdmapi" => Ok(Synth::Kdmapi), + "mididevice" => Ok(Synth::MidiDevice), + "none" => Ok(Synth::None), + s => Err(format!( + "{} was not expected. Expected one of `xsynth`, `kdmapi`, `mididevice` or `none`", + s + )), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, FromPrimitive)] +#[repr(usize)] +#[serde(rename_all = "lowercase")] +pub enum Statistics { + #[default] + Time = 0, + Fps = 1, + VoiceCount = 2, + Rendered = 3, + NoteCount = 4, +} + +impl Statistics { + #[inline] + pub const fn as_str(self) -> &'static str { + match self { + Statistics::Time => "Time", + Statistics::Fps => "FPS", + Statistics::VoiceCount => "Voice Count", + Statistics::Rendered => "Rendered", + Statistics::NoteCount => "Note Count", + } + } + + pub fn iter() -> Iter<'static, Statistics> { + static STATISTICS: [Statistics; 5] = [ + Statistics::Time, + Statistics::Fps, + Statistics::VoiceCount, + Statistics::Rendered, + Statistics::NoteCount, + ]; + STATISTICS.iter() + } +} + +impl FromStr for Statistics { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "time" => Ok(Statistics::Time), + "fps" => Ok(Statistics::Fps), + "voicecount" => Ok(Statistics::VoiceCount), + "rendered" => Ok(Statistics::Rendered), + "notecount" => Ok(Statistics::NoteCount), + s => Err(format!("{} was not expected.", s)), + } + } +} + +#[repr(usize)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, FromPrimitive)] +#[serde(rename_all = "lowercase")] +pub enum Colors { + #[default] + Rainbow = 0, + Random = 1, + Palette = 2, +} + +impl Colors { + #[inline] + pub const fn as_str(self) -> &'static str { + match self { + Colors::Rainbow => "Rainbow", + Colors::Random => "Random", + Colors::Palette => "Palette", + } + } +} + +impl FromStr for Colors { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "rainbow" => Ok(Colors::Rainbow), + "random" => Ok(Colors::Random), + "palette" => Ok(Colors::Palette), + s => Err(format!( + "{} was not expected. Expected one of `ranbow`, `random` or `palette`", + s + )), + } + } +} diff --git a/src/settings/migrations.rs b/src/settings/migrations.rs index ea63efb..7d667d7 100644 --- a/src/settings/migrations.rs +++ b/src/settings/migrations.rs @@ -1,77 +1,6 @@ -use colors_transform::{Color, Rgb}; -use egui::Color32; -use serde_derive::Deserialize; -use std::fs; +mod serializers; +mod v0; +mod v1; -use super::{MidiLoading, MidiSettings, Synth, SynthSettings, VisualSettings, WasabiSettings}; - -#[derive(Deserialize)] -pub struct WasabiConfigFileV0 { - note_speed: f64, - bg_color: String, - bar_color: String, - random_colors: bool, - sfz_path: String, - first_key: u8, - last_key: u8, - midi_loading: usize, - buffer_ms: f64, - use_threadpool: bool, - limit_layers: bool, - layer_count: usize, - fade_out_kill: bool, - use_effects: bool, - vel_ignore_lo: u8, - vel_ignore_hi: u8, - synth: usize, -} - -impl WasabiConfigFileV0 { - pub fn migrate() -> Result { - let config_path = WasabiSettings::get_config_path(); - let content = fs::read_to_string(config_path).unwrap_or_default(); - let cfg = toml::from_str::(&content)?; - if let (Ok(bg), Ok(bar)) = ( - Rgb::from_hex_str(&cfg.bg_color), - Rgb::from_hex_str(&cfg.bar_color), - ) { - Ok(WasabiSettings { - synth: SynthSettings { - synth: Synth::from(cfg.synth), - buffer_ms: cfg.buffer_ms, - use_threadpool: cfg.use_threadpool, - limit_layers: cfg.limit_layers, - layer_count: cfg.layer_count, - fade_out_kill: cfg.fade_out_kill, - use_effects: cfg.use_effects, - sfz_path: cfg.sfz_path, - vel_ignore: cfg.vel_ignore_lo..=cfg.vel_ignore_hi, - }, - midi: MidiSettings { - note_speed: cfg.note_speed, - random_colors: cfg.random_colors, - key_range: cfg.first_key..=cfg.last_key, - midi_loading: MidiLoading::from(cfg.midi_loading), - }, - visual: VisualSettings { - bg_color: Color32::from_rgb( - bg.get_red() as u8, - bg.get_green() as u8, - bg.get_blue() as u8, - ), - bar_color: Color32::from_rgb( - bar.get_red() as u8, - bar.get_green() as u8, - bar.get_blue() as u8, - ), - show_top_pannel: true, - show_statistics: true, - fullscreen: false, - }, - load_midi_file: None, - }) - } else { - Ok(WasabiSettings::default()) - } - } -} +pub use v0::*; +pub use v1::*; diff --git a/src/settings/migrations/serializers.rs b/src/settings/migrations/serializers.rs new file mode 100644 index 0000000..9339616 --- /dev/null +++ b/src/settings/migrations/serializers.rs @@ -0,0 +1,104 @@ +pub mod color32_serde { + use colors_transform::{Color, Rgb}; + use egui::Color32; + use serde::{de::Visitor, Deserializer}; + + #[inline(always)] + pub fn color_parser(s: &str) -> Result { + let rgb = Rgb::from_hex_str(s).map_err(|e| e.message)?; + Ok(Color32::from_rgb( + rgb.get_red() as u8, + rgb.get_green() as u8, + rgb.get_blue() as u8, + )) + } + + pub struct ColorVisitor; + + impl<'de> Visitor<'de> for ColorVisitor { + type Value = Color32; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("A color encoded as a hex string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + color_parser(v) + .map_err(|e| E::invalid_value(serde::de::Unexpected::Str(v), &e.as_str())) + } + } + + pub fn deserialize<'de, D>(de: D) -> Result + where + D: Deserializer<'de>, + { + de.deserialize_str(ColorVisitor) + } +} + +pub mod range_serde { + use std::ops::RangeInclusive; + + use serde::{ + de::{self, Visitor}, + Deserializer, + }; + + use serde_derive::Deserialize; + + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "lowercase")] + enum Field { + Hi, + Lo, + } + + pub struct RangeVisitor; + + impl<'de> Visitor<'de> for RangeVisitor { + type Value = RangeInclusive; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("A color encoded as a hex string") + } + + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut hi = None; + let mut lo = None; + while let Some(key) = map.next_key::()? { + match key { + Field::Hi => { + if hi.is_some() { + return Err(de::Error::duplicate_field("hi")); + } + hi = Some(map.next_value::()?); + } + Field::Lo => { + if lo.is_some() { + return Err(de::Error::duplicate_field("lo")); + } + lo = Some(map.next_value::()?); + } + } + } + + let hi = hi.ok_or_else(|| de::Error::missing_field("hi"))?; + let lo = lo.ok_or_else(|| de::Error::missing_field("lo"))?; + + Ok(hi..=lo) + } + } + + pub fn deserialize<'de, D>(de: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + de.deserialize_struct("range", &["hi", "lo"], RangeVisitor) + } +} diff --git a/src/settings/migrations/v0.rs b/src/settings/migrations/v0.rs new file mode 100644 index 0000000..0a796e2 --- /dev/null +++ b/src/settings/migrations/v0.rs @@ -0,0 +1,71 @@ +use colors_transform::{Color, Rgb}; +use egui::Color32; +use serde_derive::Deserialize; + +use super::v1::{MidiSettingsV1, SynthSettingsV1, VisualSettingsV1}; +use super::WasabiConfigFileV1; +use crate::settings::enums::{MidiParsing, Synth}; + +#[allow(dead_code)] +#[derive(Deserialize)] +pub struct WasabiConfigFileV0 { + note_speed: f64, + bg_color: String, + bar_color: String, + random_colors: bool, + sfz_path: String, + first_key: u8, + last_key: u8, + midi_loading: usize, + buffer_ms: f64, + limit_layers: bool, + layer_count: usize, + fade_out_kill: bool, + linear_envelope: bool, + use_effects: bool, + vel_ignore_lo: u8, + vel_ignore_hi: u8, + synth: usize, +} + +impl WasabiConfigFileV0 { + pub fn migrate_to_v1(content: String) -> Result { + let cfg = toml::from_str::(&content)?; + let bg = Rgb::from_hex_str(&cfg.bg_color).unwrap_or(Rgb::from(0.1, 0.1, 0.1)); + let bar = Rgb::from_hex_str(&cfg.bar_color).unwrap_or(Rgb::from(0.56, 0.0, 0.0)); + Ok(WasabiConfigFileV1 { + synth: SynthSettingsV1 { + synth: Synth::from(cfg.synth), + buffer_ms: cfg.buffer_ms, + limit_layers: cfg.limit_layers, + layer_count: cfg.layer_count, + fade_out_kill: cfg.fade_out_kill, + use_effects: cfg.use_effects, + sfz_path: cfg.sfz_path, + vel_ignore: cfg.vel_ignore_lo..=cfg.vel_ignore_hi, + }, + midi: MidiSettingsV1 { + note_speed: cfg.note_speed, + random_colors: cfg.random_colors, + key_range: cfg.first_key..=cfg.last_key, + midi_loading: MidiParsing::from(cfg.midi_loading), + }, + visual: VisualSettingsV1 { + bg_color: Color32::from_rgb( + bg.get_red() as u8, + bg.get_green() as u8, + bg.get_blue() as u8, + ), + bar_color: Color32::from_rgb( + bar.get_red() as u8, + bar.get_green() as u8, + bar.get_blue() as u8, + ), + show_top_pannel: true, + show_statistics: true, + fullscreen: false, + }, + load_midi_file: None, + }) + } +} diff --git a/src/settings/migrations/v1.rs b/src/settings/migrations/v1.rs new file mode 100644 index 0000000..f209e63 --- /dev/null +++ b/src/settings/migrations/v1.rs @@ -0,0 +1,101 @@ +use egui::Color32; +use serde_derive::Deserialize; +use std::ops::RangeInclusive; +use xsynth_realtime::{ChannelInitOptions, XSynthRealtimeConfig}; + +use super::serializers::{color32_serde, range_serde}; +use crate::settings::{ + MidiParsing, MidiSettings, SceneSettings, Synth, SynthSettings, WasabiSettings, + WasabiSoundfont, XSynthSettings, +}; + +#[allow(dead_code)] +#[derive(Deserialize)] +pub struct VisualSettingsV1 { + #[serde(with = "color32_serde")] + pub bg_color: Color32, + #[serde(with = "color32_serde")] + pub bar_color: Color32, + pub show_top_pannel: bool, + pub show_statistics: bool, + pub fullscreen: bool, +} + +#[allow(dead_code)] +#[derive(Deserialize)] +pub struct MidiSettingsV1 { + pub note_speed: f64, + pub random_colors: bool, + #[serde(with = "range_serde")] + pub key_range: RangeInclusive, + pub midi_loading: MidiParsing, +} + +#[allow(dead_code)] +#[derive(Deserialize)] +pub struct SynthSettingsV1 { + pub synth: Synth, + pub buffer_ms: f64, + pub sfz_path: String, + pub limit_layers: bool, + pub layer_count: usize, + #[serde(with = "range_serde")] + pub vel_ignore: RangeInclusive, + pub fade_out_kill: bool, + pub use_effects: bool, +} + +#[allow(dead_code)] +#[derive(Deserialize)] +pub struct WasabiConfigFileV1 { + pub synth: SynthSettingsV1, + pub midi: MidiSettingsV1, + pub visual: VisualSettingsV1, + #[serde(skip_serializing_if = "Option::is_none")] + pub load_midi_file: Option, +} + +impl WasabiConfigFileV1 { + pub fn migrate_to_v2(content: String) -> Result { + let cfg = toml::from_str::(&content)?; + Ok(Self::migrate_to_v2_raw(cfg)) + } + + pub fn migrate_to_v2_raw(cfg: Self) -> WasabiSettings { + WasabiSettings { + scene: SceneSettings { + bg_color: cfg.visual.bg_color, + bar_color: cfg.visual.bar_color, + statistics: Default::default(), + note_speed: cfg.midi.note_speed, + key_range: cfg.midi.key_range, + }, + midi: MidiSettings { + parsing: cfg.midi.midi_loading, + ..Default::default() + }, + synth: SynthSettings { + synth: cfg.synth.synth, + soundfonts: vec![WasabiSoundfont { + path: cfg.synth.sfz_path.into(), + enabled: true, + options: Default::default(), + }], + xsynth: XSynthSettings { + layers: cfg.synth.layer_count, + limit_layers: cfg.synth.limit_layers, + config: XSynthRealtimeConfig { + render_window_ms: cfg.synth.buffer_ms, + channel_init_options: ChannelInitOptions { + fade_out_killing: cfg.synth.fade_out_kill, + }, + ignore_range: cfg.synth.vel_ignore, + ..Default::default() + }, + }, + ..Default::default() + }, + ..Default::default() + } + } +} diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 58f8923..1429424 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -1,8 +1,5 @@ -use clap::{parser::ValueSource, value_parser, Arg, ArgAction, Command, ValueHint}; -use colors_transform::{Color, Rgb}; use directories::BaseDirs; use egui::Color32; -use num_enum::FromPrimitive; use serde_derive::{Deserialize, Serialize}; use std::{ fmt::Debug, @@ -10,639 +7,247 @@ use std::{ io::Write, ops::RangeInclusive, path::{Path, PathBuf}, - str::FromStr, }; -use xsynth_core::{channel::ChannelInitOptions, soundfont::SoundfontInitOptions}; +use xsynth_core::soundfont::SoundfontInitOptions; use xsynth_realtime::XSynthRealtimeConfig; +mod enums; mod migrations; -#[inline(always)] -fn f64_parser(s: &str) -> Result { - s.parse().map_err(|e| format!("{}", e)) -} +pub use enums::*; -#[inline(always)] -fn note_speed(s: &str) -> Result { - let num: f64 = f64_parser(s)?; - if (0.0001..=2.0).contains(&num) { - Ok(2.0001 - num) - } else { - Err(String::from("Number must be between >0 and 2.0")) - } -} +// region: gui -#[inline(always)] -fn color_parser(s: &str) -> Result { - let rgb = Rgb::from_hex_str(s).map_err(|e| e.message)?; - Ok(Color32::from_rgb( - rgb.get_red() as u8, - rgb.get_green() as u8, - rgb.get_blue() as u8, - )) -} - -#[inline(always)] -fn range_parser(s: &str) -> Result, String> { - let range = s - .split_once(',') - .ok_or_else(|| String::from("This argument requires 2 numbers, comma seperated"))?; - - Ok(range.0.parse().map_err(|e| format!("{}", e))? - ..=range.1.parse().map_err(|e| format!("{}", e))?) +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(default)] +pub struct GuiSettings { + pub check_for_updates: bool, + pub fps_limit: usize, + pub skip_control: f64, + pub speed_control: f64, } -mod color32_serde { - use colors_transform::Rgb; - use egui::Color32; - use serde::{de::Visitor, Deserializer, Serializer}; - - use super::color_parser; - - pub fn serialize(color: &Color32, ser: S) -> Result - where - S: Serializer, - { - let hex_color = - Rgb::from(color.r() as f32, color.g() as f32, color.b() as f32).to_css_hex_string(); - - ser.serialize_str(&hex_color) - } - - pub struct ColorVisitor; - - impl<'de> Visitor<'de> for ColorVisitor { - type Value = Color32; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("A color encoded as a hex string") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - color_parser(v) - .map_err(|e| E::invalid_value(serde::de::Unexpected::Str(v), &e.as_str())) +impl Default for GuiSettings { + fn default() -> Self { + Self { + check_for_updates: true, + fps_limit: 0, + skip_control: 1.0, + speed_control: 0.05, } } - - pub fn deserialize<'de, D>(de: D) -> Result - where - D: Deserializer<'de>, - { - de.deserialize_str(ColorVisitor) - } } -mod range_serde { - use std::ops::RangeInclusive; - - use serde::{ - de::{self, Visitor}, - ser::SerializeStruct, - Deserializer, Serializer, - }; - - use serde_derive::Deserialize; - - pub fn serialize(range: &RangeInclusive, ser: S) -> Result - where - S: Serializer, - { - let mut ser_struct = ser.serialize_struct("range", 2)?; - ser_struct.serialize_field("hi", range.start())?; - ser_struct.serialize_field("lo", range.end())?; - ser_struct.end() - } - - #[derive(Deserialize)] - #[serde(field_identifier, rename_all = "lowercase")] - enum Field { - Hi, - Lo, - } - - pub struct RangeVisitor; - - impl<'de> Visitor<'de> for RangeVisitor { - type Value = RangeInclusive; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("A color encoded as a hex string") - } - - fn visit_map(self, mut map: A) -> Result - where - A: serde::de::MapAccess<'de>, - { - let mut hi = None; - let mut lo = None; - while let Some(key) = map.next_key::()? { - match key { - Field::Hi => { - if hi.is_some() { - return Err(de::Error::duplicate_field("hi")); - } - hi = Some(map.next_value::()?); - } - Field::Lo => { - if lo.is_some() { - return Err(de::Error::duplicate_field("lo")); - } - lo = Some(map.next_value::()?); - } - } - } - - let hi = hi.ok_or_else(|| de::Error::missing_field("hi"))?; - let lo = lo.ok_or_else(|| de::Error::missing_field("lo"))?; +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(default)] +pub struct StatisticsSettings { + pub border: bool, + pub floating: bool, + pub opacity: f32, + pub order: Vec<(Statistics, bool)>, +} - Ok(hi..=lo) +impl Default for StatisticsSettings { + fn default() -> Self { + Self { + border: true, + floating: true, + opacity: 0.5, + order: Statistics::iter().map(|i| (*i, true)).collect(), } } - - pub fn deserialize<'de, D>(de: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - de.deserialize_struct("range", &["hi", "lo"], RangeVisitor) - } } -#[repr(usize)] -#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, FromPrimitive)] -#[serde(rename_all = "lowercase")] -pub enum MidiLoading { - #[default] - Ram = 0, - Live = 1, - Cake = 2, +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(default)] +pub struct SceneSettings { + pub bg_color: Color32, + pub bar_color: Color32, + pub statistics: StatisticsSettings, + pub note_speed: f64, + pub key_range: RangeInclusive, } -impl MidiLoading { - pub const fn as_str(self) -> &'static str { - match self { - MidiLoading::Ram => "In RAM", - MidiLoading::Live => "Live", - MidiLoading::Cake => "Cake", +impl Default for SceneSettings { + fn default() -> Self { + Self { + bg_color: Color32::from_rgb(30, 30, 30), + bar_color: Color32::from_rgb(145, 0, 0), + statistics: Default::default(), + note_speed: 0.25, + key_range: 0..=127, } } } -impl FromStr for MidiLoading { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "ram" => Ok(MidiLoading::Ram), - "live" => Ok(MidiLoading::Live), - "cake" => Ok(MidiLoading::Cake), - s => Err(format!( - "{} was not expected. Expected one of `ram`, `live` or `cake`", - s - )), - } - } -} +// endregion -#[repr(usize)] -#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, FromPrimitive)] -#[serde(rename_all = "lowercase")] -pub enum Synth { - #[default] - XSynth = 0, - Kdmapi = 1, -} +// region: midi -impl Synth { - #[inline] - pub const fn as_str(self) -> &'static str { - match self { - Synth::XSynth => "XSynth", - Synth::Kdmapi => "KDMAPI", - } - } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(default)] +pub struct MidiSettings { + pub parsing: MidiParsing, + pub colors: Colors, + pub palette_path: PathBuf, } -impl FromStr for Synth { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - "xsynth" => Ok(Synth::XSynth), - "kdmapi" => Ok(Synth::Kdmapi), - s => Err(format!( - "{} was not expected. Expected one of `xsynth`, or `kdmapi`", - s - )), +impl Default for MidiSettings { + fn default() -> Self { + Self { + parsing: MidiParsing::Cake, + colors: Colors::Rainbow, + palette_path: PathBuf::new(), } } } -#[derive(Debug, Deserialize, Serialize)] +// endregion + +// region: synth + +#[derive(Debug, Serialize, Deserialize, Clone)] #[serde(default)] -pub struct VisualSettings { - #[serde(with = "color32_serde")] - pub bg_color: Color32, - #[serde(with = "color32_serde")] - pub bar_color: Color32, - pub show_top_pannel: bool, - pub show_statistics: bool, - pub fullscreen: bool, +pub struct XSynthSettings { + pub config: XSynthRealtimeConfig, + pub limit_layers: bool, + pub layers: usize, } -impl Default for VisualSettings { +impl Default for XSynthSettings { fn default() -> Self { - VisualSettings { - bg_color: Color32::from_rgb(30, 30, 30), - bar_color: Color32::from_rgb(145, 0, 0), - show_top_pannel: true, - show_statistics: true, - fullscreen: false, + Self { + config: Default::default(), + limit_layers: true, + layers: 4, } } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[serde(default)] -pub struct MidiSettings { - pub note_speed: f64, - pub random_colors: bool, - #[serde(with = "range_serde")] - pub key_range: RangeInclusive, - pub midi_loading: MidiLoading, +pub struct KdmapiSettings { + pub use_om_sflist: bool, } -impl Default for MidiSettings { - fn default() -> Self { - MidiSettings { - note_speed: 0.25, - random_colors: false, - key_range: 0..=127, - midi_loading: MidiLoading::Cake, - } - } +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[serde(default)] +pub struct WasabiSoundfont { + pub path: PathBuf, + pub enabled: bool, + pub options: SoundfontInitOptions, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] #[serde(default)] pub struct SynthSettings { pub synth: Synth, - pub buffer_ms: f64, - pub use_threadpool: bool, - pub sfz_path: String, - pub limit_layers: bool, - pub layer_count: usize, - #[serde(with = "range_serde")] - pub vel_ignore: RangeInclusive, - pub fade_out_kill: bool, - pub use_effects: bool, + pub soundfonts: Vec, + + pub xsynth: XSynthSettings, + pub kdmapi: KdmapiSettings, + pub midi_device: String, } impl Default for SynthSettings { fn default() -> Self { - SynthSettings { + Self { synth: Synth::XSynth, - buffer_ms: XSynthRealtimeConfig::default().render_window_ms, - use_threadpool: false, - sfz_path: String::new(), - limit_layers: true, - layer_count: 4, - vel_ignore: 0..=0, - fade_out_kill: ChannelInitOptions::default().fade_out_killing, - use_effects: SoundfontInitOptions::default().use_effects, + soundfonts: Vec::new(), + xsynth: Default::default(), + kdmapi: Default::default(), + midi_device: String::new(), } } } -#[derive(Debug, Deserialize, Serialize, Default)] +// endregion + +// region: general + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] #[serde(default)] pub struct WasabiSettings { - pub synth: SynthSettings, + pub gui: GuiSettings, + pub scene: SceneSettings, pub midi: MidiSettings, - pub visual: VisualSettings, - #[serde(skip_serializing_if = "Option::is_none")] - pub load_midi_file: Option, + pub synth: SynthSettings, } -static CONFIG_PATH: &str = "wasabi-config.toml"; - impl WasabiSettings { + const VERSION_TEXT: &str = "# DON'T EDIT THIS LINE; Version: 2\n"; + pub fn new_or_load() -> Self { let config_path = Self::get_config_path(); - let mut config = if !Path::new(&config_path).exists() { - Self::load_and_save_defaults() - } else { - let config = fs::read_to_string(&config_path).unwrap(); - if config.starts_with('#') { - if let Ok(config) = toml::from_str(&config) { - config - } else { - Self::load_and_save_defaults() + if !Path::new(&config_path).exists() { + return Self::load_and_save_defaults(); + } else if let Ok(config) = fs::read_to_string(&config_path) { + if config.starts_with(Self::VERSION_TEXT) { + let offset = Self::VERSION_TEXT.len(); + if let Ok(config) = serde_json::from_str(&config[offset..]) { + return config; + } + } else if config.starts_with("# DON'T EDIT THIS LINE; Version: 1") { + if let Ok(cfg) = migrations::WasabiConfigFileV1::migrate_to_v2(config) { + cfg.save_to_file(); + return cfg; } } else { - let config = migrations::WasabiConfigFileV0::migrate().unwrap_or_default(); - config.save_to_file(); - config + if let Ok(v1) = migrations::WasabiConfigFileV0::migrate_to_v1(config) { + let cfg = migrations::WasabiConfigFileV1::migrate_to_v2_raw(v1); + cfg.save_to_file(); + return cfg; + } } - }; + } - config.augment_from_args(); - config + println!("Error loading config. Resetting."); + Self::load_and_save_defaults() } pub fn save_to_file(&self) { let config_path = Self::get_config_path(); - let toml: String = toml::to_string(&self).unwrap(); - if Path::new(&config_path).exists() { - fs::remove_file(&config_path).expect("Error deleting old config"); - } - let mut file = fs::File::create(&config_path).unwrap(); - file.write_all(b"# DON'T EDIT THIS LINE; Version: 1\n\n") - .unwrap(); - file.write_all(toml.as_bytes()) - .expect("Error creating config"); - } - - fn augment_from_args(&mut self) { - let matches = Command::new("wasabi") - .version(env!("CARGO_PKG_VERSION")) - .author(env!("CARGO_PKG_AUTHORS")) - .about(env!("CARGO_PKG_DESCRIPTION")) - .arg( - Arg::new("synth") - .help("The synthesizer to use") - .long_help( - "The synthesizer that is used to play the MIDI. \ - This can either be XSynth (recommended) or KDMAPI. KDMAPI \ - only works if you have OmniMIDI installed, and are using Windows", - ) - .short('S') - .long("synth") - .value_parser(Synth::from_str), - ) - .arg( - Arg::new("buffer-ms") - .help("The amount of time events are held in the buffer") - .long_help( - "The amount of time that events are held in the \ - buffer before being played. Higher numbers may increase \ - performance but also increase latency.", - ) - .short('b') - .long("buffer-ms") - .value_parser(f64_parser), - ) - .arg( - Arg::new("sfz-path") - .help("The path to an SFZ SoundFont") - .long_help( - "The path to any SFZ soundfont. In audio only mode \ - a soundfont must be passed either via the config file, or - this command line option. In the GUI you can set this under \ - `Open Synth Settings > SFZ Path`", - ) - .short('s') - .long("sfz-path") - .value_hint(ValueHint::FilePath), - ) - .arg( - Arg::new("dont-limit-layers") - .help("Do not apply the layer limit to the synth") - .long_help( - "This allows the synth to create as many layers as it \ - needs to play the MIDI file faithfully. Only turn this on if your \ - MIDI sounds bad, or your computer is running on GFuel", - ) - .long("dont-limit-layers") - .action(ArgAction::SetFalse), - ) - .arg( - Arg::new("layer-count") - .help("The amount of layers that the synthesizer can create") - .long_help( - "The maximum amount of voices the synth is \ - allowed to create per key per channel", - ) - .short('l') - .long("layer-count") - .value_parser(value_parser!(usize)), - ) - .arg( - Arg::new("vel-ignore") - .help("The range of note velocities that the synth will discard") - .long_help( - "Two numbers, comma seperated, that represent a range of velocities \ - that the synth will discard, making notes in the range inaudible.", - ) - .short('v') - .long("vel-ignore") - .value_parser(range_parser), - ) - .arg( - Arg::new("fade-out-kill") - .help("Once a voice is killed, fade it out") - .long_help( - "Once the synthesizer kills one of it's voices, it will fade it \ - out as opposed to simply cutting it off", - ) - .short('F') - .long("fade-out-kill") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("no-effects") - .help("Disables the soundfont's effects") - .long_help( - "Disables soundfont audio effects. \ - This may improve the performance.", - ) - .short('N') - .long("no-effects") - .action(ArgAction::SetFalse), - ) - .arg( - Arg::new("note-speed") - .help("The speed that the notes travel on-screen") - .long_help( - "The speed at which the notes will move across the screen. This makes \ - the notes physically longer, causing them to move faster on-screen", - ) - .short('n') - .long("note-speed") - .value_parser(note_speed), - ) - .arg( - Arg::new("random-colors") - .help("Make each channel a random color") - .long_help("This causes each of the note channels to become a random color") - .short('r') - .long("random-colors") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("key-range") - .help("The key range of the on-screen piano keyboard") - .long_help( - "Two numbers, comma seperated, that describe the range \ - of keys to be shown on the on-screen piano keyboard, the range must \ - be less than 255 and more than 0", - ) - .short('k') - .long("key-range") - .value_parser(range_parser), - ) - .arg( - Arg::new("midi-loading") - .help("How the MIDI is loaded into `wasabi`") - .long_help( - "The method in which the MIDI file is loaded into `wasabi`, the \ - two possible options are `ram`, which loads the MIDI file entirely into \ - RAM before beginning playback; and `live` which will read the MIDI file \ - as it's being played back. The latter method is for using with systems \ - with low memory", - ) - .short('m') - .long("midi-loading") - .value_parser(MidiLoading::from_str), - ) - .arg( - Arg::new("bg-color") - .help("The window background") - .long_help("A hex color string describing the background color of the window") - .long("bg-color") - .value_parser(color_parser), - ) - .arg( - Arg::new("bar-color") - .help("The color of the bar just above the piano") - .long_help( - "A hex color string describing the color of the bar just above \ - the on-screen piano keyboard", - ) - .long("bar-color") - .value_parser(color_parser), - ) - .arg( - Arg::new("hide-top-pannel") - .long_help( - "Hides the top panel from view when the app opens. It can be un-hidden \ - with Ctrl+F", - ) - .help("Hide the top panel") - .long("hide-top-pannel") - .action(ArgAction::SetFalse), - ) - .arg( - Arg::new("hide-statistics") - .help("Hide the statistics window") - .long_help( - "Hides the statistics window from view when the app opens. It can be \ - un-hidden with Ctrl+G", - ) - .long("hide-statistics") - .action(ArgAction::SetFalse), - ) - .arg( - Arg::new("fullscreen") - .help("Start `wasabi` in fullscreen") - .long_help( - "Starts `wasabi` in fullscreen mode. `wasabi` will use \ - borderless fullscreen mode on Linux systems running Wayland, \ - and exclusive fullscreen mode for everyone else", - ) - .short('f') - .long("fullscreen") - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new("midi-file") - .value_hint(ValueHint::FilePath) - .help("The MIDI file to immediately begin playing") - .long_help( - "This MIDI file is played immediately after the app's launch. \ - This argument is required to use the `--audio-only` option", - ), - ) - .get_matches(); - - macro_rules! set { - ($one:ident.$two:ident,$value:expr) => { - if let Some(value) = matches.get_one($value) { - self.$one.$two = *value; - } - }; - } - - macro_rules! set_flag { - ($one:ident.$two:ident,$value:expr) => { - if matches!(matches.value_source($value), Some(ValueSource::CommandLine)) { - if let Some(value) = matches.get_one($value) { - self.$one.$two = *value; - } - } - }; - } - - macro_rules! set_owned { - ($one:ident.$two:ident,$value:expr,$type:ty) => { - if let Some(value) = matches.get_one::<$type>($value) { - self.$one.$two = value.to_owned(); - } - }; + let cfg: String = serde_json::to_string_pretty(&self).unwrap(); + if let Ok(mut file) = fs::File::create(&config_path) { + file.write_all(Self::VERSION_TEXT.as_bytes()).unwrap(); + file.write_all(cfg.as_bytes()) + .expect("Error creating config"); } - - self.load_midi_file = matches.get_one::("midi-file").map(|f| f.to_owned()); - - // Synth settings - set!(synth.synth, "synth"); - set!(synth.buffer_ms, "buffer-ms"); - set_owned!(synth.sfz_path, "sfz-path", String); - set_flag!(synth.limit_layers, "dont-limit-layers"); - set!(synth.layer_count, "layer-count"); - set_owned!(synth.vel_ignore, "vel-ignore", RangeInclusive); - set_flag!(synth.fade_out_kill, "fade-out-kill"); - set_flag!(synth.use_effects, "no-effects"); - - // MIDI settings - set!(midi.note_speed, "note-speed"); - set_flag!(midi.random_colors, "random-colors"); - set_owned!(midi.key_range, "key-range", RangeInclusive); - set!(midi.midi_loading, "midi-loading"); - - // Visual settings - set!(visual.bg_color, "bg-color"); - set!(visual.bar_color, "bar-color"); - set_flag!(visual.show_top_pannel, "hide-top-pannel"); - set_flag!(visual.show_statistics, "hide-statistics"); - set_flag!(visual.fullscreen, "fullscreen"); } fn load_and_save_defaults() -> Self { - let _ = fs::remove_file(Self::get_config_path()); let cfg = Self::default(); Self::save_to_file(&cfg); cfg } - fn get_config_path() -> String { + fn get_config_dir() -> PathBuf { if let Some(base_dirs) = BaseDirs::new() { let mut path: PathBuf = base_dirs.config_dir().to_path_buf(); path.push("wasabi"); - path.push(CONFIG_PATH); - if std::fs::create_dir_all(path.parent().unwrap()).is_ok() { - if let Some(path) = path.to_str() { - path.to_string() - } else { - CONFIG_PATH.to_string() - } - } else { - CONFIG_PATH.to_string() + if std::fs::create_dir_all(&path).is_ok() { + return path; } - } else { - CONFIG_PATH.to_string() } + + PathBuf::from("./") + } + + fn get_config_path() -> PathBuf { + let mut path = Self::get_config_dir(); + path.push("wasabi-config.toml"); + + path + } + + pub fn get_palettes_dir() -> PathBuf { + let mut path = Self::get_config_dir(); + path.push("palettes"); + std::fs::create_dir_all(&path).unwrap_or_default(); + + path } } + +// endregion diff --git a/src/state.rs b/src/state.rs index c652a76..d9c5dbe 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,10 +1,46 @@ use std::path::PathBuf; -#[derive(Clone, Default)] +#[derive(Default, PartialEq)] +pub enum SettingsTab { + #[default] + Visual, + Midi, + Synth, + SoundFonts, +} + pub struct WasabiState { pub fullscreen: bool, - pub settings_visible: bool, - pub xsynth_settings_visible: bool, - pub last_midi_file: Option, - // pub last_sfz_file: Option, + + pub panel_pinned: bool, + pub panel_popup_id: egui::Id, + pub stats_visible: bool, + + pub show_settings: bool, + pub show_shortcuts: bool, + pub show_about: bool, + + pub settings_tab: SettingsTab, + + pub last_location: PathBuf, +} + +impl Default for WasabiState { + fn default() -> Self { + Self { + fullscreen: false, + + panel_pinned: true, + panel_popup_id: egui::Id::new("options_popup"), + stats_visible: true, + + show_settings: false, + show_shortcuts: false, + show_about: false, + + settings_tab: SettingsTab::default(), + + last_location: PathBuf::default(), + } + } } diff --git a/src/utils.rs b/src/utils.rs index cff1fa6..6afb453 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,22 @@ pub fn calculate_border_width(width_pixels: f32, keys_len: f32) -> f32 { ((width_pixels / keys_len) / 12.0).clamp(1.0, 5.0).round() } + +pub fn convert_seconds_to_time_string(sec: f64) -> String { + let time_millis = (sec * 10.0) as i64 % 10; + let time_sec = sec as i64 % 60; + let time_min = sec as i64 / 60; + + format!( + "{}{:0width$}:{:0width$}.{}", + if time_sec + time_millis < 0 { + '-' + } else { + '\0' + }, + time_min.abs(), + time_sec.abs(), + time_millis.abs(), + width = 2 + ) +}