mirror of
https://github.com/robbert-vdh/nih-plug.git
synced 2026-07-01 02:36:54 +00:00
add "bring your own gui" example plugins
This commit is contained in:
453
Cargo.lock
generated
453
Cargo.lock
generated
@@ -129,6 +129,12 @@ version = "0.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
|
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alsa"
|
name = "alsa"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -321,6 +327,15 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
|
checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ash"
|
||||||
|
version = "0.38.0+1.3.281"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
|
||||||
|
dependencies = [
|
||||||
|
"libloading 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "assert_no_alloc"
|
name = "assert_no_alloc"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@@ -665,7 +680,7 @@ version = "0.1.0"
|
|||||||
source = "git+https://github.com/RustAudio/baseview.git?rev=1d9806d5bd92275d0d8142d9c9c90198757b9b25#1d9806d5bd92275d0d8142d9c9c90198757b9b25"
|
source = "git+https://github.com/RustAudio/baseview.git?rev=1d9806d5bd92275d0d8142d9c9c90198757b9b25#1d9806d5bd92275d0d8142d9c9c90198757b9b25"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
"keyboard-types",
|
"keyboard-types",
|
||||||
"nix 0.22.3",
|
"nix 0.22.3",
|
||||||
"objc",
|
"objc",
|
||||||
@@ -683,7 +698,7 @@ version = "0.1.0"
|
|||||||
source = "git+https://github.com/RustAudio/baseview.git?rev=2c1b1a7b0fef1a29a5150a6a8f6fef6a0cbab8c4#2c1b1a7b0fef1a29a5150a6a8f6fef6a0cbab8c4"
|
source = "git+https://github.com/RustAudio/baseview.git?rev=2c1b1a7b0fef1a29a5150a6a8f6fef6a0cbab8c4#2c1b1a7b0fef1a29a5150a6a8f6fef6a0cbab8c4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
"keyboard-types",
|
"keyboard-types",
|
||||||
"nix 0.22.3",
|
"nix 0.22.3",
|
||||||
"objc",
|
"objc",
|
||||||
@@ -701,7 +716,7 @@ version = "0.1.0"
|
|||||||
source = "git+https://github.com/RustAudio/baseview.git?rev=579130ecb4f9f315ae52190af42f0ea46aeaa4a2#579130ecb4f9f315ae52190af42f0ea46aeaa4a2"
|
source = "git+https://github.com/RustAudio/baseview.git?rev=579130ecb4f9f315ae52190af42f0ea46aeaa4a2#579130ecb4f9f315ae52190af42f0ea46aeaa4a2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
"keyboard-types",
|
"keyboard-types",
|
||||||
"nix 0.22.3",
|
"nix 0.22.3",
|
||||||
"objc",
|
"objc",
|
||||||
@@ -718,7 +733,24 @@ version = "0.1.0"
|
|||||||
source = "git+https://github.com/RustAudio/baseview.git?rev=9a0b42c09d712777b2edb4c5e0cb6baf21e988f0#9a0b42c09d712777b2edb4c5e0cb6baf21e988f0"
|
source = "git+https://github.com/RustAudio/baseview.git?rev=9a0b42c09d712777b2edb4c5e0cb6baf21e988f0#9a0b42c09d712777b2edb4c5e0cb6baf21e988f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
|
"keyboard-types",
|
||||||
|
"nix 0.22.3",
|
||||||
|
"objc",
|
||||||
|
"raw-window-handle 0.5.2",
|
||||||
|
"uuid",
|
||||||
|
"winapi",
|
||||||
|
"x11",
|
||||||
|
"x11rb 0.13.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "baseview"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/RustAudio/baseview.git?rev=9a0b42c09d712777b2edb4c5e0cb6baf21e988f0#9a0b42c09d712777b2edb4c5e0cb6baf21e988f0"
|
||||||
|
dependencies = [
|
||||||
|
"cocoa",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
"keyboard-types",
|
"keyboard-types",
|
||||||
"nix 0.22.3",
|
"nix 0.22.3",
|
||||||
"objc",
|
"objc",
|
||||||
@@ -747,6 +779,12 @@ dependencies = [
|
|||||||
"syn 2.0.98",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-vec"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@@ -1092,12 +1130,22 @@ checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"block",
|
"block",
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
"core-graphics-types",
|
"core-graphics-types 0.1.3",
|
||||||
"libc",
|
"libc",
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "codespan-reporting"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||||
|
dependencies = [
|
||||||
|
"termcolor",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "color_quant"
|
name = "color_quant"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -1195,6 +1243,16 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@@ -1234,7 +1292,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
|
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-graphics-types"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"core-foundation 0.10.0",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1276,7 +1345,7 @@ version = "0.6.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a7847ca018a67204508b77cb9e6de670125075f7464fff5f673023378fa34f5"
|
checksum = "1a7847ca018a67204508b77cb9e6de670125075f7464fff5f673023378fa34f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"coremidi-sys",
|
"coremidi-sys",
|
||||||
]
|
]
|
||||||
@@ -1459,6 +1528,12 @@ dependencies = [
|
|||||||
"syn 2.0.98",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctor-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cty"
|
name = "cty"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -2366,11 +2441,11 @@ dependencies = [
|
|||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"cfg_aliases 0.1.1",
|
"cfg_aliases 0.1.1",
|
||||||
"cgl",
|
"cgl",
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
"dispatch",
|
"dispatch",
|
||||||
"glutin_egl_sys",
|
"glutin_egl_sys",
|
||||||
"glutin_glx_sys",
|
"glutin_glx_sys",
|
||||||
"glutin_wgl_sys",
|
"glutin_wgl_sys 0.4.0",
|
||||||
"libloading 0.7.4",
|
"libloading 0.7.4",
|
||||||
"objc2 0.3.0-beta.3.patch-leaks.3",
|
"objc2 0.3.0-beta.3.patch-leaks.3",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -2420,6 +2495,15 @@ dependencies = [
|
|||||||
"gl_generator",
|
"gl_generator",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glutin_wgl_sys"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a4e1951bbd9434a81aa496fe59ccc2235af3820d27b85f9314e279609211e2c"
|
||||||
|
dependencies = [
|
||||||
|
"gl_generator",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glyph_brush"
|
name = "glyph_brush"
|
||||||
version = "0.7.12"
|
version = "0.7.12"
|
||||||
@@ -2469,6 +2553,57 @@ dependencies = [
|
|||||||
"scroll",
|
"scroll",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gpu-alloc"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"gpu-alloc-types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gpu-alloc-types"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gpu-allocator"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"presser",
|
||||||
|
"thiserror",
|
||||||
|
"windows 0.58.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gpu-descriptor"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"gpu-descriptor-types",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gpu-descriptor-types"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.2"
|
version = "0.15.2"
|
||||||
@@ -2508,6 +2643,12 @@ version = "0.4.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hexf-parse"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.61"
|
version = "0.1.61"
|
||||||
@@ -2811,6 +2952,17 @@ dependencies = [
|
|||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "khronos-egl"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libloading 0.8.5",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "khronos_api"
|
name = "khronos_api"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
@@ -3056,6 +3208,21 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "metal"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"block",
|
||||||
|
"core-graphics-types 0.1.3",
|
||||||
|
"foreign-types 0.5.0",
|
||||||
|
"log",
|
||||||
|
"objc",
|
||||||
|
"paste",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "midi-consts"
|
name = "midi-consts"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -3122,6 +3289,27 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "naga"
|
||||||
|
version = "23.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d5941e45a15b53aad4375eedf02033adb7a28931eedc31117faffa52e6a857e"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec 0.7.6",
|
||||||
|
"bit-set",
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"cfg_aliases 0.1.1",
|
||||||
|
"codespan-reporting",
|
||||||
|
"hexf-parse",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"rustc-hash 1.1.0",
|
||||||
|
"spirv",
|
||||||
|
"termcolor",
|
||||||
|
"thiserror",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndk"
|
name = "ndk"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -3228,7 +3416,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"clap",
|
"clap",
|
||||||
"clap-sys",
|
"clap-sys",
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
"cpal",
|
"cpal",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"jack",
|
"jack",
|
||||||
@@ -4145,6 +4333,12 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pollster"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "poly_mod_synth"
|
name = "poly_mod_synth"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -4175,6 +4369,12 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "presser"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "primal-check"
|
name = "primal-check"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -4349,6 +4549,12 @@ dependencies = [
|
|||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "range-alloc"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rangemap"
|
name = "rangemap"
|
||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
@@ -4937,6 +5143,34 @@ dependencies = [
|
|||||||
"nih_plug",
|
"nih_plug",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "softbuffer"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08"
|
||||||
|
dependencies = [
|
||||||
|
"as-raw-xcb-connection",
|
||||||
|
"bytemuck",
|
||||||
|
"cfg_aliases 0.2.1",
|
||||||
|
"core-graphics 0.24.0",
|
||||||
|
"drm",
|
||||||
|
"fastrand 2.1.0",
|
||||||
|
"foreign-types 0.5.0",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"objc2 0.5.2",
|
||||||
|
"objc2-foundation",
|
||||||
|
"objc2-quartz-core",
|
||||||
|
"raw-window-handle 0.6.2",
|
||||||
|
"redox_syscall 0.5.3",
|
||||||
|
"rustix 0.38.34",
|
||||||
|
"tiny-xlib",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
"x11rb 0.13.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spectral_compressor"
|
name = "spectral_compressor"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -4951,6 +5185,15 @@ dependencies = [
|
|||||||
"triple_buffer",
|
"triple_buffer",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spirv"
|
||||||
|
version = "0.3.0+sdk-1.3.268.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -5132,6 +5375,18 @@ dependencies = [
|
|||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-xlib"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e"
|
||||||
|
dependencies = [
|
||||||
|
"as-raw-xcb-connection",
|
||||||
|
"ctor-lite",
|
||||||
|
"pkg-config",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -5831,6 +6086,112 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wgpu"
|
||||||
|
version = "23.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec 0.7.6",
|
||||||
|
"cfg_aliases 0.1.1",
|
||||||
|
"document-features",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"naga",
|
||||||
|
"parking_lot 0.12.3",
|
||||||
|
"profiling",
|
||||||
|
"raw-window-handle 0.6.2",
|
||||||
|
"smallvec",
|
||||||
|
"static_assertions",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"wgpu-core",
|
||||||
|
"wgpu-hal",
|
||||||
|
"wgpu-types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wgpu-core"
|
||||||
|
version = "23.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec 0.7.6",
|
||||||
|
"bit-vec",
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"cfg_aliases 0.1.1",
|
||||||
|
"document-features",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"naga",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot 0.12.3",
|
||||||
|
"profiling",
|
||||||
|
"raw-window-handle 0.6.2",
|
||||||
|
"rustc-hash 1.1.0",
|
||||||
|
"smallvec",
|
||||||
|
"thiserror",
|
||||||
|
"wgpu-hal",
|
||||||
|
"wgpu-types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wgpu-hal"
|
||||||
|
version = "23.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"arrayvec 0.7.6",
|
||||||
|
"ash",
|
||||||
|
"bit-set",
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"block",
|
||||||
|
"bytemuck",
|
||||||
|
"cfg_aliases 0.1.1",
|
||||||
|
"core-graphics-types 0.1.3",
|
||||||
|
"glow 0.14.2",
|
||||||
|
"glutin_wgl_sys 0.6.0",
|
||||||
|
"gpu-alloc",
|
||||||
|
"gpu-allocator",
|
||||||
|
"gpu-descriptor",
|
||||||
|
"js-sys",
|
||||||
|
"khronos-egl",
|
||||||
|
"libc",
|
||||||
|
"libloading 0.8.5",
|
||||||
|
"log",
|
||||||
|
"metal",
|
||||||
|
"naga",
|
||||||
|
"ndk-sys 0.5.0+25.2.9519653",
|
||||||
|
"objc",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot 0.12.3",
|
||||||
|
"profiling",
|
||||||
|
"range-alloc",
|
||||||
|
"raw-window-handle 0.6.2",
|
||||||
|
"renderdoc-sys",
|
||||||
|
"rustc-hash 1.1.0",
|
||||||
|
"smallvec",
|
||||||
|
"thiserror",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
"wgpu-types",
|
||||||
|
"windows 0.58.0",
|
||||||
|
"windows-core 0.58.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wgpu-types"
|
||||||
|
version = "23.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"js-sys",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "widestring"
|
name = "widestring"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -5907,8 +6268,8 @@ version = "0.48.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement 0.48.0",
|
||||||
"windows-interface",
|
"windows-interface 0.48.0",
|
||||||
"windows-targets 0.48.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5922,6 +6283,16 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.58.0",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
@@ -5937,7 +6308,20 @@ version = "0.54.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
|
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-result",
|
"windows-result 0.1.2",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement 0.58.0",
|
||||||
|
"windows-interface 0.58.0",
|
||||||
|
"windows-result 0.2.0",
|
||||||
|
"windows-strings",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5952,6 +6336,17 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.90",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-interface"
|
name = "windows-interface"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
@@ -5963,6 +6358,17 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.90",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -5972,6 +6378,25 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||||
|
dependencies = [
|
||||||
|
"windows-result 0.2.0",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.42.0"
|
version = "0.42.0"
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ members = [
|
|||||||
"cargo_nih_plug",
|
"cargo_nih_plug",
|
||||||
"xtask",
|
"xtask",
|
||||||
|
|
||||||
|
"plugins/examples/byo_gui_gl",
|
||||||
|
"plugins/examples/byo_gui_softbuffer",
|
||||||
|
"plugins/examples/byo_gui_wgpu",
|
||||||
"plugins/examples/gain",
|
"plugins/examples/gain",
|
||||||
"plugins/examples/gain_gui_egui",
|
"plugins/examples/gain_gui_egui",
|
||||||
"plugins/examples/gain_gui_iced",
|
"plugins/examples/gain_gui_iced",
|
||||||
|
|||||||
22
plugins/examples/byo_gui_gl/Cargo.toml
Normal file
22
plugins/examples/byo_gui_gl/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "byo_gui_gl"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Billy Messenger <60663878+BillyDM@users.noreply.github.com>"]
|
||||||
|
license = "ISC"
|
||||||
|
|
||||||
|
description = "A simple example plugin with a raw OpenGL context for rendering"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
# The `lib` artifact is needed for the standalone target
|
||||||
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nih_plug = { path = "../../../", features = ["assert_process_allocs", "standalone"] }
|
||||||
|
baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "9a0b42c09d712777b2edb4c5e0cb6baf21e988f0", features = ["opengl"] }
|
||||||
|
raw-window-handle = "0.5"
|
||||||
|
glow = "0.16"
|
||||||
|
crossbeam = "0.8"
|
||||||
|
atomic_float = "0.1"
|
||||||
|
# To make the state persistable
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
553
plugins/examples/byo_gui_gl/src/lib.rs
Normal file
553
plugins/examples/byo_gui_gl/src/lib.rs
Normal file
@@ -0,0 +1,553 @@
|
|||||||
|
//! This plugin demonstrates how to "bring your own GUI toolkit" using a raw OpenGL context.
|
||||||
|
|
||||||
|
use baseview::{gl::GlConfig, WindowHandle, WindowOpenOptions, WindowScalePolicy};
|
||||||
|
use crossbeam::atomic::AtomicCell;
|
||||||
|
use nih_plug::params::persist::PersistentField;
|
||||||
|
use nih_plug::prelude::*;
|
||||||
|
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The time it takes for the peak meter to decay by 12 dB after switching to complete silence.
|
||||||
|
const PEAK_METER_DECAY_MS: f64 = 150.0;
|
||||||
|
|
||||||
|
pub struct CustomGlWindow {
|
||||||
|
gui_context: Arc<dyn GuiContext>,
|
||||||
|
gl: Arc<glow::Context>,
|
||||||
|
|
||||||
|
vertex_array: glow::NativeVertexArray,
|
||||||
|
program: glow::NativeProgram,
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
#[allow(unused)]
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CustomGlWindow {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
use glow::HasContext as _;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
self.gl.delete_program(self.program);
|
||||||
|
self.gl.delete_vertex_array(self.vertex_array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomGlWindow {
|
||||||
|
fn new(
|
||||||
|
window: &mut baseview::Window<'_>,
|
||||||
|
gui_context: Arc<dyn GuiContext>,
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
_scaling_factor: f32,
|
||||||
|
) -> Self {
|
||||||
|
use glow::HasContext as _;
|
||||||
|
|
||||||
|
// TODO: Return an error instead of panicking once baseview gets thats
|
||||||
|
// ability.
|
||||||
|
let gl_context = window
|
||||||
|
.gl_context()
|
||||||
|
.expect("failed to get baseview gl context");
|
||||||
|
|
||||||
|
let (gl, vertex_array, program) = unsafe {
|
||||||
|
gl_context.make_current();
|
||||||
|
|
||||||
|
#[allow(clippy::arc_with_non_send_sync)]
|
||||||
|
let gl = Arc::new(glow::Context::from_loader_function(|s| {
|
||||||
|
gl_context.get_proc_address(s)
|
||||||
|
}));
|
||||||
|
|
||||||
|
let vertex_array = gl
|
||||||
|
.create_vertex_array()
|
||||||
|
.expect("Cannot create vertex array");
|
||||||
|
gl.bind_vertex_array(Some(vertex_array));
|
||||||
|
|
||||||
|
let program = gl.create_program().expect("Cannot create program");
|
||||||
|
|
||||||
|
let (vertex_shader_source, fragment_shader_source) = (
|
||||||
|
r#"const vec2 verts[3] = vec2[3](
|
||||||
|
vec2(0.5f, 1.0f),
|
||||||
|
vec2(0.0f, 0.0f),
|
||||||
|
vec2(1.0f, 0.0f)
|
||||||
|
);
|
||||||
|
out vec2 vert;
|
||||||
|
void main() {
|
||||||
|
vert = verts[gl_VertexID];
|
||||||
|
gl_Position = vec4(vert - 0.5, 0.0, 1.0);
|
||||||
|
}"#,
|
||||||
|
r#"precision mediump float;
|
||||||
|
in vec2 vert;
|
||||||
|
out vec4 color;
|
||||||
|
void main() {
|
||||||
|
color = vec4(vert, 0.5, 1.0);
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let shader_sources = [
|
||||||
|
(glow::VERTEX_SHADER, vertex_shader_source),
|
||||||
|
(glow::FRAGMENT_SHADER, fragment_shader_source),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut shaders = Vec::with_capacity(shader_sources.len());
|
||||||
|
|
||||||
|
for (shader_type, shader_source) in shader_sources.iter() {
|
||||||
|
let shader = gl
|
||||||
|
.create_shader(*shader_type)
|
||||||
|
.expect("Cannot create shader");
|
||||||
|
gl.shader_source(shader, &format!("{}\n{}", "#version 130", shader_source));
|
||||||
|
gl.compile_shader(shader);
|
||||||
|
if !gl.get_shader_compile_status(shader) {
|
||||||
|
panic!("{}", gl.get_shader_info_log(shader));
|
||||||
|
}
|
||||||
|
gl.attach_shader(program, shader);
|
||||||
|
shaders.push(shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.link_program(program);
|
||||||
|
if !gl.get_program_link_status(program) {
|
||||||
|
panic!("{}", gl.get_program_info_log(program));
|
||||||
|
}
|
||||||
|
|
||||||
|
for shader in shaders {
|
||||||
|
gl.detach_shader(program, shader);
|
||||||
|
gl.delete_shader(shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.use_program(Some(program));
|
||||||
|
|
||||||
|
gl_context.make_not_current();
|
||||||
|
|
||||||
|
(gl, vertex_array, program)
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
gui_context,
|
||||||
|
gl,
|
||||||
|
vertex_array,
|
||||||
|
program,
|
||||||
|
params,
|
||||||
|
peak_meter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl baseview::WindowHandler for CustomGlWindow {
|
||||||
|
fn on_frame(&mut self, window: &mut baseview::Window) {
|
||||||
|
use glow::HasContext as _;
|
||||||
|
// Do rendering here.
|
||||||
|
|
||||||
|
let (_width, _height) = self.params.editor_state.size();
|
||||||
|
|
||||||
|
let gl_context = window
|
||||||
|
.gl_context()
|
||||||
|
.expect("failed to get baseview gl context");
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl_context.make_current();
|
||||||
|
|
||||||
|
self.gl.clear_color(0.05, 0.05, 0.05, 1.0);
|
||||||
|
self.gl.clear(glow::COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
self.gl.draw_arrays(glow::TRIANGLES, 0, 3);
|
||||||
|
|
||||||
|
gl_context.swap_buffers();
|
||||||
|
gl_context.make_not_current();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
_window: &mut baseview::Window,
|
||||||
|
event: baseview::Event,
|
||||||
|
) -> baseview::EventStatus {
|
||||||
|
// Use this to set parameter values.
|
||||||
|
let _param_setter = ParamSetter::new(self.gui_context.as_ref());
|
||||||
|
|
||||||
|
match &event {
|
||||||
|
// Do event processing here.
|
||||||
|
baseview::Event::Window(event) => match event {
|
||||||
|
baseview::WindowEvent::Resized(window_info) => {
|
||||||
|
self.params.editor_state.size.store((
|
||||||
|
window_info.logical_size().width.round() as u32,
|
||||||
|
window_info.logical_size().height.round() as u32,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseview::EventStatus::Captured
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct CustomGlEditorState {
|
||||||
|
/// The window's size in logical pixels before applying `scale_factor`.
|
||||||
|
#[serde(with = "nih_plug::params::persist::serialize_atomic_cell")]
|
||||||
|
size: AtomicCell<(u32, u32)>,
|
||||||
|
/// Whether the editor's window is currently open.
|
||||||
|
#[serde(skip)]
|
||||||
|
open: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomGlEditorState {
|
||||||
|
pub fn from_size(size: (u32, u32)) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
size: AtomicCell::new(size),
|
||||||
|
open: AtomicBool::new(false),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a `(width, height)` pair for the current size of the GUI in logical pixels.
|
||||||
|
pub fn size(&self) -> (u32, u32) {
|
||||||
|
self.size.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the GUI is currently visible.
|
||||||
|
// Called `is_open()` instead of `open()` to avoid the ambiguity.
|
||||||
|
pub fn is_open(&self) -> bool {
|
||||||
|
self.open.load(Ordering::Acquire)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PersistentField<'a, CustomGlEditorState> for Arc<CustomGlEditorState> {
|
||||||
|
fn set(&self, new_value: CustomGlEditorState) {
|
||||||
|
self.size.store(new_value.size.load());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: Fn(&CustomGlEditorState) -> R,
|
||||||
|
{
|
||||||
|
f(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CustomGlEditor {
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
|
||||||
|
/// The scaling factor reported by the host, if any. On macOS this will never be set and we
|
||||||
|
/// should use the system scaling factor instead.
|
||||||
|
scaling_factor: AtomicCell<Option<f32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Editor for CustomGlEditor {
|
||||||
|
fn spawn(
|
||||||
|
&self,
|
||||||
|
parent: ParentWindowHandle,
|
||||||
|
context: Arc<dyn GuiContext>,
|
||||||
|
) -> Box<dyn std::any::Any + Send> {
|
||||||
|
let (unscaled_width, unscaled_height) = self.params.editor_state.size();
|
||||||
|
let scaling_factor = self.scaling_factor.load();
|
||||||
|
|
||||||
|
let gui_context = Arc::clone(&context);
|
||||||
|
|
||||||
|
let params = Arc::clone(&self.params);
|
||||||
|
let peak_meter = Arc::clone(&self.peak_meter);
|
||||||
|
|
||||||
|
let window = baseview::Window::open_parented(
|
||||||
|
&ParentWindowHandleAdapter(parent),
|
||||||
|
WindowOpenOptions {
|
||||||
|
title: String::from("OpenGL Window"),
|
||||||
|
// Baseview should be doing the DPI scaling for us
|
||||||
|
size: baseview::Size::new(unscaled_width as f64, unscaled_height as f64),
|
||||||
|
// NOTE: For some reason passing 1.0 here causes the UI to be scaled on macOS but
|
||||||
|
// not the mouse events.
|
||||||
|
scale: scaling_factor
|
||||||
|
.map(|factor| WindowScalePolicy::ScaleFactor(factor as f64))
|
||||||
|
.unwrap_or(WindowScalePolicy::SystemScaleFactor),
|
||||||
|
|
||||||
|
gl_config: Some(GlConfig {
|
||||||
|
version: (3, 2),
|
||||||
|
red_bits: 8,
|
||||||
|
blue_bits: 8,
|
||||||
|
green_bits: 8,
|
||||||
|
alpha_bits: 8,
|
||||||
|
depth_bits: 24,
|
||||||
|
stencil_bits: 8,
|
||||||
|
samples: None,
|
||||||
|
srgb: true,
|
||||||
|
double_buffer: true,
|
||||||
|
vsync: false,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
move |window: &mut baseview::Window<'_>| -> CustomGlWindow {
|
||||||
|
CustomGlWindow::new(
|
||||||
|
window,
|
||||||
|
gui_context,
|
||||||
|
params,
|
||||||
|
peak_meter,
|
||||||
|
scaling_factor.unwrap_or(1.0),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
self.params.editor_state.open.store(true, Ordering::Release);
|
||||||
|
Box::new(CustomGlEditorHandle {
|
||||||
|
state: self.params.editor_state.clone(),
|
||||||
|
window,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> (u32, u32) {
|
||||||
|
self.params.editor_state.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_scale_factor(&self, factor: f32) -> bool {
|
||||||
|
// If the editor is currently open then the host must not change the current HiDPI scale as
|
||||||
|
// we don't have a way to handle that. Ableton Live does this.
|
||||||
|
if self.params.editor_state.is_open() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scaling_factor.store(Some(factor));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_value_changed(&self, _id: &str, _normalized_value: f32) {
|
||||||
|
// As mentioned above, for now we'll always force a redraw to allow meter widgets to work
|
||||||
|
// correctly. In the future we can use an `Arc<AtomicBool>` and only force a redraw when
|
||||||
|
// that boolean is set.
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_modulation_changed(&self, _id: &str, _modulation_offset: f32) {}
|
||||||
|
|
||||||
|
fn param_values_changed(&self) {
|
||||||
|
// Same
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The window handle used for [`EguiEditor`].
|
||||||
|
struct CustomGlEditorHandle {
|
||||||
|
state: Arc<CustomGlEditorState>,
|
||||||
|
window: WindowHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around
|
||||||
|
/// having this requirement?
|
||||||
|
unsafe impl Send for CustomGlEditorHandle {}
|
||||||
|
|
||||||
|
impl Drop for CustomGlEditorHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.state.open.store(false, Ordering::Release);
|
||||||
|
// XXX: This should automatically happen when the handle gets dropped, but apparently not
|
||||||
|
self.window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This version of `baseview` uses a different version of `raw_window_handle than NIH-plug, so we
|
||||||
|
/// need to adapt it ourselves.
|
||||||
|
struct ParentWindowHandleAdapter(nih_plug::editor::ParentWindowHandle);
|
||||||
|
|
||||||
|
unsafe impl HasRawWindowHandle for ParentWindowHandleAdapter {
|
||||||
|
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||||
|
match self.0 {
|
||||||
|
ParentWindowHandle::X11Window(window) => {
|
||||||
|
let mut handle = raw_window_handle::XcbWindowHandle::empty();
|
||||||
|
handle.window = window;
|
||||||
|
RawWindowHandle::Xcb(handle)
|
||||||
|
}
|
||||||
|
ParentWindowHandle::AppKitNsView(ns_view) => {
|
||||||
|
let mut handle = raw_window_handle::AppKitWindowHandle::empty();
|
||||||
|
handle.ns_view = ns_view;
|
||||||
|
RawWindowHandle::AppKit(handle)
|
||||||
|
}
|
||||||
|
ParentWindowHandle::Win32Hwnd(hwnd) => {
|
||||||
|
let mut handle = raw_window_handle::Win32WindowHandle::empty();
|
||||||
|
handle.hwnd = hwnd;
|
||||||
|
RawWindowHandle::Win32(handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is mostly identical to the gain example, minus some fluff, and with a GUI.
|
||||||
|
pub struct MyPlugin {
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
|
||||||
|
/// Needed to normalize the peak meter's response based on the sample rate.
|
||||||
|
peak_meter_decay_weight: f32,
|
||||||
|
/// The current data for the peak meter. This is stored as an [`Arc`] so we can share it between
|
||||||
|
/// the GUI and the audio processing parts. If you have more state to share, then it's a good
|
||||||
|
/// idea to put all of that in a struct behind a single `Arc`.
|
||||||
|
///
|
||||||
|
/// This is stored as voltage gain.
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Params)]
|
||||||
|
pub struct MyPluginParams {
|
||||||
|
/// The editor state, saved together with the parameter state so the custom scaling can be
|
||||||
|
/// restored.
|
||||||
|
#[persist = "editor-state"]
|
||||||
|
editor_state: Arc<CustomGlEditorState>,
|
||||||
|
|
||||||
|
#[id = "gain"]
|
||||||
|
pub gain: FloatParam,
|
||||||
|
|
||||||
|
#[id = "foobar"]
|
||||||
|
pub some_int: IntParam,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MyPlugin {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
params: Arc::new(MyPluginParams::default()),
|
||||||
|
|
||||||
|
peak_meter_decay_weight: 1.0,
|
||||||
|
peak_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MyPluginParams {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
editor_state: CustomGlEditorState::from_size((400, 300)),
|
||||||
|
|
||||||
|
// See the main gain example for more details
|
||||||
|
gain: FloatParam::new(
|
||||||
|
"Gain",
|
||||||
|
util::db_to_gain(0.0),
|
||||||
|
FloatRange::Skewed {
|
||||||
|
min: util::db_to_gain(-30.0),
|
||||||
|
max: util::db_to_gain(30.0),
|
||||||
|
factor: FloatRange::gain_skew_factor(-30.0, 30.0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_smoother(SmoothingStyle::Logarithmic(50.0))
|
||||||
|
.with_unit(" dB")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_gain_to_db(2))
|
||||||
|
.with_string_to_value(formatters::s2v_f32_gain_to_db()),
|
||||||
|
some_int: IntParam::new("Something", 3, IntRange::Linear { min: 0, max: 3 }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for MyPlugin {
|
||||||
|
const NAME: &'static str = "BYO GUI Example (OpenGL)";
|
||||||
|
const VENDOR: &'static str = "Moist Plugins GmbH";
|
||||||
|
const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ";
|
||||||
|
const EMAIL: &'static str = "info@example.com";
|
||||||
|
|
||||||
|
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
|
||||||
|
AudioIOLayout {
|
||||||
|
main_input_channels: NonZeroU32::new(2),
|
||||||
|
main_output_channels: NonZeroU32::new(2),
|
||||||
|
..AudioIOLayout::const_default()
|
||||||
|
},
|
||||||
|
AudioIOLayout {
|
||||||
|
main_input_channels: NonZeroU32::new(1),
|
||||||
|
main_output_channels: NonZeroU32::new(1),
|
||||||
|
..AudioIOLayout::const_default()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const SAMPLE_ACCURATE_AUTOMATION: bool = true;
|
||||||
|
|
||||||
|
type SysExMessage = ();
|
||||||
|
type BackgroundTask = ();
|
||||||
|
|
||||||
|
fn params(&self) -> Arc<dyn Params> {
|
||||||
|
self.params.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
|
||||||
|
Some(Box::new(CustomGlEditor {
|
||||||
|
params: Arc::clone(&self.params),
|
||||||
|
peak_meter: Arc::clone(&self.peak_meter),
|
||||||
|
|
||||||
|
// TODO: We can't get the size of the window when baseview does its own scaling, so if the
|
||||||
|
// host does not set a scale factor on Windows or Linux we should just use a factor of
|
||||||
|
// 1. That may make the GUI tiny but it also prevents it from getting cut off.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
scaling_factor: AtomicCell::new(None),
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
scaling_factor: AtomicCell::new(Some(1.0)),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize(
|
||||||
|
&mut self,
|
||||||
|
_audio_io_layout: &AudioIOLayout,
|
||||||
|
buffer_config: &BufferConfig,
|
||||||
|
_context: &mut impl InitContext<Self>,
|
||||||
|
) -> bool {
|
||||||
|
// After `PEAK_METER_DECAY_MS` milliseconds of pure silence, the peak meter's value should
|
||||||
|
// have dropped by 12 dB
|
||||||
|
self.peak_meter_decay_weight = 0.25f64
|
||||||
|
.powf((buffer_config.sample_rate as f64 * PEAK_METER_DECAY_MS / 1000.0).recip())
|
||||||
|
as f32;
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(
|
||||||
|
&mut self,
|
||||||
|
buffer: &mut Buffer,
|
||||||
|
_aux: &mut AuxiliaryBuffers,
|
||||||
|
_context: &mut impl ProcessContext<Self>,
|
||||||
|
) -> ProcessStatus {
|
||||||
|
for channel_samples in buffer.iter_samples() {
|
||||||
|
let mut amplitude = 0.0;
|
||||||
|
let num_samples = channel_samples.len();
|
||||||
|
|
||||||
|
let gain = self.params.gain.smoothed.next();
|
||||||
|
for sample in channel_samples {
|
||||||
|
*sample *= gain;
|
||||||
|
amplitude += *sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To save resources, a plugin can (and probably should!) only perform expensive
|
||||||
|
// calculations that are only displayed on the GUI while the GUI is open
|
||||||
|
if self.params.editor_state.is_open() {
|
||||||
|
amplitude = (amplitude / num_samples as f32).abs();
|
||||||
|
let current_peak_meter = self.peak_meter.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let new_peak_meter = if amplitude > current_peak_meter {
|
||||||
|
amplitude
|
||||||
|
} else {
|
||||||
|
current_peak_meter * self.peak_meter_decay_weight
|
||||||
|
+ amplitude * (1.0 - self.peak_meter_decay_weight)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.peak_meter
|
||||||
|
.store(new_peak_meter, std::sync::atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessStatus::Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClapPlugin for MyPlugin {
|
||||||
|
const CLAP_ID: &'static str = "com.moist-plugins-gmbh.byo-gui-gl";
|
||||||
|
const CLAP_DESCRIPTION: Option<&'static str> =
|
||||||
|
Some("A simple example plugin with a raw OpenGL context for rendering");
|
||||||
|
const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL);
|
||||||
|
const CLAP_SUPPORT_URL: Option<&'static str> = None;
|
||||||
|
const CLAP_FEATURES: &'static [ClapFeature] = &[
|
||||||
|
ClapFeature::AudioEffect,
|
||||||
|
ClapFeature::Stereo,
|
||||||
|
ClapFeature::Mono,
|
||||||
|
ClapFeature::Utility,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vst3Plugin for MyPlugin {
|
||||||
|
const VST3_CLASS_ID: [u8; 16] = *b"ByoGuiOpenGLWooo";
|
||||||
|
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
|
||||||
|
&[Vst3SubCategory::Fx, Vst3SubCategory::Tools];
|
||||||
|
}
|
||||||
|
|
||||||
|
nih_export_clap!(MyPlugin);
|
||||||
|
nih_export_vst3!(MyPlugin);
|
||||||
7
plugins/examples/byo_gui_gl/src/main.rs
Normal file
7
plugins/examples/byo_gui_gl/src/main.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
use nih_plug::prelude::*;
|
||||||
|
|
||||||
|
use byo_gui_gl::MyPlugin;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
nih_export_standalone::<MyPlugin>();
|
||||||
|
}
|
||||||
23
plugins/examples/byo_gui_softbuffer/Cargo.toml
Normal file
23
plugins/examples/byo_gui_softbuffer/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "byo_gui_softbuffer"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Billy Messenger <60663878+BillyDM@users.noreply.github.com>"]
|
||||||
|
license = "ISC"
|
||||||
|
|
||||||
|
description = "A simple example plugin with a raw Softbuffer context for rendering"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
# The `lib` artifact is needed for the standalone target
|
||||||
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nih_plug = { path = "../../../", features = ["assert_process_allocs", "standalone"] }
|
||||||
|
baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "9a0b42c09d712777b2edb4c5e0cb6baf21e988f0" }
|
||||||
|
softbuffer = { version = "0.4.6", default-features = false, features = ["kms", "x11"]}
|
||||||
|
raw-window-handle = "0.5"
|
||||||
|
raw-window-handle-06 = { package = "raw-window-handle", version = "0.6" }
|
||||||
|
crossbeam = "0.8"
|
||||||
|
atomic_float = "0.1"
|
||||||
|
# To make the state persistable
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
585
plugins/examples/byo_gui_softbuffer/src/lib.rs
Normal file
585
plugins/examples/byo_gui_softbuffer/src/lib.rs
Normal file
@@ -0,0 +1,585 @@
|
|||||||
|
//! This plugin demonstrates how to "bring your own GUI toolkit" using a raw Softbuffer rendering context.
|
||||||
|
|
||||||
|
use baseview::{WindowHandle, WindowOpenOptions, WindowScalePolicy};
|
||||||
|
use crossbeam::atomic::AtomicCell;
|
||||||
|
use nih_plug::params::persist::PersistentField;
|
||||||
|
use nih_plug::prelude::*;
|
||||||
|
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
num::NonZeroIsize,
|
||||||
|
ptr::NonNull,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The time it takes for the peak meter to decay by 12 dB after switching to complete silence.
|
||||||
|
const PEAK_METER_DECAY_MS: f64 = 150.0;
|
||||||
|
|
||||||
|
pub struct CustomSoftbufferWindow {
|
||||||
|
gui_context: Arc<dyn GuiContext>,
|
||||||
|
|
||||||
|
_sb_context: softbuffer::Context<SoftbufferWindowHandleAdapter>,
|
||||||
|
sb_surface: softbuffer::Surface<SoftbufferWindowHandleAdapter, SoftbufferWindowHandleAdapter>,
|
||||||
|
|
||||||
|
physical_width: u32,
|
||||||
|
physical_height: u32,
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
#[allow(unused)]
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomSoftbufferWindow {
|
||||||
|
fn new(
|
||||||
|
window: &mut baseview::Window<'_>,
|
||||||
|
gui_context: Arc<dyn GuiContext>,
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
scaling_factor: f32,
|
||||||
|
) -> Self {
|
||||||
|
let (unscaled_width, unscaled_height) = params.editor_state.size();
|
||||||
|
let physical_width = (unscaled_width as f64 * scaling_factor as f64).round() as u32;
|
||||||
|
let physical_height = (unscaled_height as f64 * scaling_factor as f64).round() as u32;
|
||||||
|
|
||||||
|
let target = baseview_window_to_surface_target(window);
|
||||||
|
|
||||||
|
let sb_context =
|
||||||
|
softbuffer::Context::new(target.clone()).expect("could not get softbuffer context");
|
||||||
|
let mut sb_surface = softbuffer::Surface::new(&sb_context, target)
|
||||||
|
.expect("could not create softbuffer surface");
|
||||||
|
|
||||||
|
sb_surface
|
||||||
|
.resize(
|
||||||
|
NonZeroU32::new(physical_width).unwrap(),
|
||||||
|
NonZeroU32::new(physical_height).unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
gui_context,
|
||||||
|
_sb_context: sb_context,
|
||||||
|
sb_surface,
|
||||||
|
physical_width,
|
||||||
|
physical_height,
|
||||||
|
params,
|
||||||
|
peak_meter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl baseview::WindowHandler for CustomSoftbufferWindow {
|
||||||
|
fn on_frame(&mut self, _window: &mut baseview::Window) {
|
||||||
|
// Do rendering here.
|
||||||
|
|
||||||
|
let mut buffer = self.sb_surface.buffer_mut().unwrap();
|
||||||
|
for y in 0..self.physical_height {
|
||||||
|
for x in 0..self.physical_width {
|
||||||
|
let red = x % 255;
|
||||||
|
let green = y % 255;
|
||||||
|
let blue = (x * y) % 255;
|
||||||
|
let index = y as usize * self.physical_width as usize + x as usize;
|
||||||
|
buffer[index] = blue | (green << 8) | (red << 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.present().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
_window: &mut baseview::Window,
|
||||||
|
event: baseview::Event,
|
||||||
|
) -> baseview::EventStatus {
|
||||||
|
// Use this to set parameter values.
|
||||||
|
let _param_setter = ParamSetter::new(self.gui_context.as_ref());
|
||||||
|
|
||||||
|
match &event {
|
||||||
|
// Do event processing here.
|
||||||
|
baseview::Event::Window(event) => match event {
|
||||||
|
baseview::WindowEvent::Resized(window_info) => {
|
||||||
|
self.params.editor_state.size.store((
|
||||||
|
window_info.logical_size().width.round() as u32,
|
||||||
|
window_info.logical_size().height.round() as u32,
|
||||||
|
));
|
||||||
|
|
||||||
|
self.physical_width = window_info.physical_size().width;
|
||||||
|
self.physical_height = window_info.physical_size().height;
|
||||||
|
|
||||||
|
self.sb_surface
|
||||||
|
.resize(
|
||||||
|
NonZeroU32::new(self.physical_width).unwrap(),
|
||||||
|
NonZeroU32::new(self.physical_height).unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseview::EventStatus::Captured
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct CustomSoftbufferEditorState {
|
||||||
|
/// The window's size in logical pixels before applying `scale_factor`.
|
||||||
|
#[serde(with = "nih_plug::params::persist::serialize_atomic_cell")]
|
||||||
|
size: AtomicCell<(u32, u32)>,
|
||||||
|
/// Whether the editor's window is currently open.
|
||||||
|
#[serde(skip)]
|
||||||
|
open: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomSoftbufferEditorState {
|
||||||
|
pub fn from_size(size: (u32, u32)) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
size: AtomicCell::new(size),
|
||||||
|
open: AtomicBool::new(false),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a `(width, height)` pair for the current size of the GUI in logical pixels.
|
||||||
|
pub fn size(&self) -> (u32, u32) {
|
||||||
|
self.size.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the GUI is currently visible.
|
||||||
|
// Called `is_open()` instead of `open()` to avoid the ambiguity.
|
||||||
|
pub fn is_open(&self) -> bool {
|
||||||
|
self.open.load(Ordering::Acquire)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PersistentField<'a, CustomSoftbufferEditorState> for Arc<CustomSoftbufferEditorState> {
|
||||||
|
fn set(&self, new_value: CustomSoftbufferEditorState) {
|
||||||
|
self.size.store(new_value.size.load());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: Fn(&CustomSoftbufferEditorState) -> R,
|
||||||
|
{
|
||||||
|
f(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CustomSoftbufferEditor {
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
|
||||||
|
/// The scaling factor reported by the host, if any. On macOS this will never be set and we
|
||||||
|
/// should use the system scaling factor instead.
|
||||||
|
scaling_factor: AtomicCell<Option<f32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Editor for CustomSoftbufferEditor {
|
||||||
|
fn spawn(
|
||||||
|
&self,
|
||||||
|
parent: ParentWindowHandle,
|
||||||
|
context: Arc<dyn GuiContext>,
|
||||||
|
) -> Box<dyn std::any::Any + Send> {
|
||||||
|
let (unscaled_width, unscaled_height) = self.params.editor_state.size();
|
||||||
|
let scaling_factor = self.scaling_factor.load();
|
||||||
|
|
||||||
|
let gui_context = Arc::clone(&context);
|
||||||
|
|
||||||
|
let params = Arc::clone(&self.params);
|
||||||
|
let peak_meter = Arc::clone(&self.peak_meter);
|
||||||
|
|
||||||
|
let window = baseview::Window::open_parented(
|
||||||
|
&ParentWindowHandleAdapter(parent),
|
||||||
|
WindowOpenOptions {
|
||||||
|
title: String::from("Softbuffer Window"),
|
||||||
|
// Baseview should be doing the DPI scaling for us
|
||||||
|
size: baseview::Size::new(unscaled_width as f64, unscaled_height as f64),
|
||||||
|
// NOTE: For some reason passing 1.0 here causes the UI to be scaled on macOS but
|
||||||
|
// not the mouse events.
|
||||||
|
scale: scaling_factor
|
||||||
|
.map(|factor| WindowScalePolicy::ScaleFactor(factor as f64))
|
||||||
|
.unwrap_or(WindowScalePolicy::SystemScaleFactor),
|
||||||
|
},
|
||||||
|
move |window: &mut baseview::Window<'_>| -> CustomSoftbufferWindow {
|
||||||
|
CustomSoftbufferWindow::new(
|
||||||
|
window,
|
||||||
|
gui_context,
|
||||||
|
params,
|
||||||
|
peak_meter,
|
||||||
|
scaling_factor.unwrap_or(1.0),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
self.params.editor_state.open.store(true, Ordering::Release);
|
||||||
|
Box::new(CustomSoftbufferEditorHandle {
|
||||||
|
state: self.params.editor_state.clone(),
|
||||||
|
window,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> (u32, u32) {
|
||||||
|
self.params.editor_state.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_scale_factor(&self, factor: f32) -> bool {
|
||||||
|
// If the editor is currently open then the host must not change the current HiDPI scale as
|
||||||
|
// we don't have a way to handle that. Ableton Live does this.
|
||||||
|
if self.params.editor_state.is_open() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scaling_factor.store(Some(factor));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_value_changed(&self, _id: &str, _normalized_value: f32) {
|
||||||
|
// As mentioned above, for now we'll always force a redraw to allow meter widgets to work
|
||||||
|
// correctly. In the future we can use an `Arc<AtomicBool>` and only force a redraw when
|
||||||
|
// that boolean is set.
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_modulation_changed(&self, _id: &str, _modulation_offset: f32) {}
|
||||||
|
|
||||||
|
fn param_values_changed(&self) {
|
||||||
|
// Same
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The window handle used for [`EguiEditor`].
|
||||||
|
struct CustomSoftbufferEditorHandle {
|
||||||
|
state: Arc<CustomSoftbufferEditorState>,
|
||||||
|
window: WindowHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around
|
||||||
|
/// having this requirement?
|
||||||
|
unsafe impl Send for CustomSoftbufferEditorHandle {}
|
||||||
|
|
||||||
|
impl Drop for CustomSoftbufferEditorHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.state.open.store(false, Ordering::Release);
|
||||||
|
// XXX: This should automatically happen when the handle gets dropped, but apparently not
|
||||||
|
self.window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This version of `baseview` uses a different version of `raw_window_handle than NIH-plug, so we
|
||||||
|
/// need to adapt it ourselves.
|
||||||
|
struct ParentWindowHandleAdapter(nih_plug::editor::ParentWindowHandle);
|
||||||
|
|
||||||
|
unsafe impl HasRawWindowHandle for ParentWindowHandleAdapter {
|
||||||
|
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||||
|
match self.0 {
|
||||||
|
ParentWindowHandle::X11Window(window) => {
|
||||||
|
let mut handle = raw_window_handle::XcbWindowHandle::empty();
|
||||||
|
handle.window = window;
|
||||||
|
RawWindowHandle::Xcb(handle)
|
||||||
|
}
|
||||||
|
ParentWindowHandle::AppKitNsView(ns_view) => {
|
||||||
|
let mut handle = raw_window_handle::AppKitWindowHandle::empty();
|
||||||
|
handle.ns_view = ns_view;
|
||||||
|
RawWindowHandle::AppKit(handle)
|
||||||
|
}
|
||||||
|
ParentWindowHandle::Win32Hwnd(hwnd) => {
|
||||||
|
let mut handle = raw_window_handle::Win32WindowHandle::empty();
|
||||||
|
handle.hwnd = hwnd;
|
||||||
|
RawWindowHandle::Win32(handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Softbuffer uses raw_window_handle v6, but baseview uses raw_window_handle v5, so we need to
|
||||||
|
/// adapt it ourselves.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct SoftbufferWindowHandleAdapter {
|
||||||
|
raw_display_handle: raw_window_handle_06::RawDisplayHandle,
|
||||||
|
raw_window_handle: raw_window_handle_06::RawWindowHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl raw_window_handle_06::HasDisplayHandle for SoftbufferWindowHandleAdapter {
|
||||||
|
fn display_handle(
|
||||||
|
&self,
|
||||||
|
) -> Result<raw_window_handle_06::DisplayHandle<'_>, raw_window_handle_06::HandleError> {
|
||||||
|
unsafe {
|
||||||
|
Ok(raw_window_handle_06::DisplayHandle::borrow_raw(
|
||||||
|
self.raw_display_handle,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl raw_window_handle_06::HasWindowHandle for SoftbufferWindowHandleAdapter {
|
||||||
|
fn window_handle(
|
||||||
|
&self,
|
||||||
|
) -> Result<raw_window_handle_06::WindowHandle<'_>, raw_window_handle_06::HandleError> {
|
||||||
|
unsafe {
|
||||||
|
Ok(raw_window_handle_06::WindowHandle::borrow_raw(
|
||||||
|
self.raw_window_handle,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn baseview_window_to_surface_target(
|
||||||
|
window: &baseview::Window<'_>,
|
||||||
|
) -> SoftbufferWindowHandleAdapter {
|
||||||
|
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
||||||
|
|
||||||
|
let raw_display_handle = window.raw_display_handle();
|
||||||
|
let raw_window_handle = window.raw_window_handle();
|
||||||
|
|
||||||
|
SoftbufferWindowHandleAdapter {
|
||||||
|
raw_display_handle: match raw_display_handle {
|
||||||
|
raw_window_handle::RawDisplayHandle::AppKit(_) => {
|
||||||
|
raw_window_handle_06::RawDisplayHandle::AppKit(
|
||||||
|
raw_window_handle_06::AppKitDisplayHandle::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawDisplayHandle::Xlib(handle) => {
|
||||||
|
raw_window_handle_06::RawDisplayHandle::Xlib(
|
||||||
|
raw_window_handle_06::XlibDisplayHandle::new(
|
||||||
|
NonNull::new(handle.display),
|
||||||
|
handle.screen,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawDisplayHandle::Xcb(handle) => {
|
||||||
|
raw_window_handle_06::RawDisplayHandle::Xcb(
|
||||||
|
raw_window_handle_06::XcbDisplayHandle::new(
|
||||||
|
NonNull::new(handle.connection),
|
||||||
|
handle.screen,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawDisplayHandle::Windows(_) => {
|
||||||
|
raw_window_handle_06::RawDisplayHandle::Windows(
|
||||||
|
raw_window_handle_06::WindowsDisplayHandle::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
},
|
||||||
|
raw_window_handle: match raw_window_handle {
|
||||||
|
raw_window_handle::RawWindowHandle::AppKit(handle) => {
|
||||||
|
raw_window_handle_06::RawWindowHandle::AppKit(
|
||||||
|
raw_window_handle_06::AppKitWindowHandle::new(
|
||||||
|
NonNull::new(handle.ns_view).unwrap(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawWindowHandle::Xlib(handle) => {
|
||||||
|
raw_window_handle_06::RawWindowHandle::Xlib(
|
||||||
|
raw_window_handle_06::XlibWindowHandle::new(handle.window),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawWindowHandle::Xcb(handle) => {
|
||||||
|
raw_window_handle_06::RawWindowHandle::Xcb(
|
||||||
|
raw_window_handle_06::XcbWindowHandle::new(
|
||||||
|
NonZeroU32::new(handle.window).unwrap(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawWindowHandle::Win32(handle) => {
|
||||||
|
// will this work? i have no idea!
|
||||||
|
let mut raw_handle = raw_window_handle_06::Win32WindowHandle::new(
|
||||||
|
NonZeroIsize::new(handle.hwnd as isize).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
raw_handle.hinstance = handle
|
||||||
|
.hinstance
|
||||||
|
.is_null()
|
||||||
|
.then(|| NonZeroIsize::new(handle.hinstance as isize).unwrap());
|
||||||
|
|
||||||
|
raw_window_handle_06::RawWindowHandle::Win32(raw_handle)
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is mostly identical to the gain example, minus some fluff, and with a GUI.
|
||||||
|
pub struct MyPlugin {
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
|
||||||
|
/// Needed to normalize the peak meter's response based on the sample rate.
|
||||||
|
peak_meter_decay_weight: f32,
|
||||||
|
/// The current data for the peak meter. This is stored as an [`Arc`] so we can share it between
|
||||||
|
/// the GUI and the audio processing parts. If you have more state to share, then it's a good
|
||||||
|
/// idea to put all of that in a struct behind a single `Arc`.
|
||||||
|
///
|
||||||
|
/// This is stored as voltage gain.
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Params)]
|
||||||
|
pub struct MyPluginParams {
|
||||||
|
/// The editor state, saved together with the parameter state so the custom scaling can be
|
||||||
|
/// restored.
|
||||||
|
#[persist = "editor-state"]
|
||||||
|
editor_state: Arc<CustomSoftbufferEditorState>,
|
||||||
|
|
||||||
|
#[id = "gain"]
|
||||||
|
pub gain: FloatParam,
|
||||||
|
|
||||||
|
#[id = "foobar"]
|
||||||
|
pub some_int: IntParam,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MyPlugin {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
params: Arc::new(MyPluginParams::default()),
|
||||||
|
|
||||||
|
peak_meter_decay_weight: 1.0,
|
||||||
|
peak_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MyPluginParams {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
editor_state: CustomSoftbufferEditorState::from_size((200, 150)),
|
||||||
|
|
||||||
|
// See the main gain example for more details
|
||||||
|
gain: FloatParam::new(
|
||||||
|
"Gain",
|
||||||
|
util::db_to_gain(0.0),
|
||||||
|
FloatRange::Skewed {
|
||||||
|
min: util::db_to_gain(-30.0),
|
||||||
|
max: util::db_to_gain(30.0),
|
||||||
|
factor: FloatRange::gain_skew_factor(-30.0, 30.0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_smoother(SmoothingStyle::Logarithmic(50.0))
|
||||||
|
.with_unit(" dB")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_gain_to_db(2))
|
||||||
|
.with_string_to_value(formatters::s2v_f32_gain_to_db()),
|
||||||
|
some_int: IntParam::new("Something", 3, IntRange::Linear { min: 0, max: 3 }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for MyPlugin {
|
||||||
|
const NAME: &'static str = "BYO GUI Example (Softbuffer)";
|
||||||
|
const VENDOR: &'static str = "Moist Plugins GmbH";
|
||||||
|
const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ";
|
||||||
|
const EMAIL: &'static str = "info@example.com";
|
||||||
|
|
||||||
|
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
|
||||||
|
AudioIOLayout {
|
||||||
|
main_input_channels: NonZeroU32::new(2),
|
||||||
|
main_output_channels: NonZeroU32::new(2),
|
||||||
|
..AudioIOLayout::const_default()
|
||||||
|
},
|
||||||
|
AudioIOLayout {
|
||||||
|
main_input_channels: NonZeroU32::new(1),
|
||||||
|
main_output_channels: NonZeroU32::new(1),
|
||||||
|
..AudioIOLayout::const_default()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const SAMPLE_ACCURATE_AUTOMATION: bool = true;
|
||||||
|
|
||||||
|
type SysExMessage = ();
|
||||||
|
type BackgroundTask = ();
|
||||||
|
|
||||||
|
fn params(&self) -> Arc<dyn Params> {
|
||||||
|
self.params.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
|
||||||
|
Some(Box::new(CustomSoftbufferEditor {
|
||||||
|
params: Arc::clone(&self.params),
|
||||||
|
peak_meter: Arc::clone(&self.peak_meter),
|
||||||
|
|
||||||
|
// TODO: We can't get the size of the window when baseview does its own scaling, so if the
|
||||||
|
// host does not set a scale factor on Windows or Linux we should just use a factor of
|
||||||
|
// 1. That may make the GUI tiny but it also prevents it from getting cut off.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
scaling_factor: AtomicCell::new(None),
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
scaling_factor: AtomicCell::new(Some(1.0)),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize(
|
||||||
|
&mut self,
|
||||||
|
_audio_io_layout: &AudioIOLayout,
|
||||||
|
buffer_config: &BufferConfig,
|
||||||
|
_context: &mut impl InitContext<Self>,
|
||||||
|
) -> bool {
|
||||||
|
// After `PEAK_METER_DECAY_MS` milliseconds of pure silence, the peak meter's value should
|
||||||
|
// have dropped by 12 dB
|
||||||
|
self.peak_meter_decay_weight = 0.25f64
|
||||||
|
.powf((buffer_config.sample_rate as f64 * PEAK_METER_DECAY_MS / 1000.0).recip())
|
||||||
|
as f32;
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(
|
||||||
|
&mut self,
|
||||||
|
buffer: &mut Buffer,
|
||||||
|
_aux: &mut AuxiliaryBuffers,
|
||||||
|
_context: &mut impl ProcessContext<Self>,
|
||||||
|
) -> ProcessStatus {
|
||||||
|
for channel_samples in buffer.iter_samples() {
|
||||||
|
let mut amplitude = 0.0;
|
||||||
|
let num_samples = channel_samples.len();
|
||||||
|
|
||||||
|
let gain = self.params.gain.smoothed.next();
|
||||||
|
for sample in channel_samples {
|
||||||
|
*sample *= gain;
|
||||||
|
amplitude += *sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To save resources, a plugin can (and probably should!) only perform expensive
|
||||||
|
// calculations that are only displayed on the GUI while the GUI is open
|
||||||
|
if self.params.editor_state.is_open() {
|
||||||
|
amplitude = (amplitude / num_samples as f32).abs();
|
||||||
|
let current_peak_meter = self.peak_meter.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let new_peak_meter = if amplitude > current_peak_meter {
|
||||||
|
amplitude
|
||||||
|
} else {
|
||||||
|
current_peak_meter * self.peak_meter_decay_weight
|
||||||
|
+ amplitude * (1.0 - self.peak_meter_decay_weight)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.peak_meter
|
||||||
|
.store(new_peak_meter, std::sync::atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessStatus::Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClapPlugin for MyPlugin {
|
||||||
|
const CLAP_ID: &'static str = "com.moist-plugins-gmbh.byo-gui-softbuffer";
|
||||||
|
const CLAP_DESCRIPTION: Option<&'static str> =
|
||||||
|
Some("A simple example plugin with a raw Softbuffer context for rendering");
|
||||||
|
const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL);
|
||||||
|
const CLAP_SUPPORT_URL: Option<&'static str> = None;
|
||||||
|
const CLAP_FEATURES: &'static [ClapFeature] = &[
|
||||||
|
ClapFeature::AudioEffect,
|
||||||
|
ClapFeature::Stereo,
|
||||||
|
ClapFeature::Mono,
|
||||||
|
ClapFeature::Utility,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vst3Plugin for MyPlugin {
|
||||||
|
const VST3_CLASS_ID: [u8; 16] = *b"ByoGuiSoftbuffer";
|
||||||
|
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
|
||||||
|
&[Vst3SubCategory::Fx, Vst3SubCategory::Tools];
|
||||||
|
}
|
||||||
|
|
||||||
|
nih_export_clap!(MyPlugin);
|
||||||
|
nih_export_vst3!(MyPlugin);
|
||||||
7
plugins/examples/byo_gui_softbuffer/src/main.rs
Normal file
7
plugins/examples/byo_gui_softbuffer/src/main.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
use nih_plug::prelude::*;
|
||||||
|
|
||||||
|
use byo_gui_softbuffer::MyPlugin;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
nih_export_standalone::<MyPlugin>();
|
||||||
|
}
|
||||||
24
plugins/examples/byo_gui_wgpu/Cargo.toml
Normal file
24
plugins/examples/byo_gui_wgpu/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "byo_gui_wgpu"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Billy Messenger <60663878+BillyDM@users.noreply.github.com>"]
|
||||||
|
license = "ISC"
|
||||||
|
|
||||||
|
description = "A simple example plugin with a raw WGPU context for rendering"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
# The `lib` artifact is needed for the standalone target
|
||||||
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nih_plug = { path = "../../../", features = ["assert_process_allocs", "standalone"] }
|
||||||
|
baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "9a0b42c09d712777b2edb4c5e0cb6baf21e988f0" }
|
||||||
|
wgpu = "23"
|
||||||
|
raw-window-handle = "0.5"
|
||||||
|
raw-window-handle-06 = { package = "raw-window-handle", version = "0.6" }
|
||||||
|
pollster = "0.4.0"
|
||||||
|
crossbeam = "0.8"
|
||||||
|
atomic_float = "0.1"
|
||||||
|
# To make the state persistable
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
673
plugins/examples/byo_gui_wgpu/src/lib.rs
Normal file
673
plugins/examples/byo_gui_wgpu/src/lib.rs
Normal file
@@ -0,0 +1,673 @@
|
|||||||
|
//! This plugin demonstrates how to "bring your own GUI toolkit" using a raw WGPU context.
|
||||||
|
|
||||||
|
use baseview::{WindowHandle, WindowOpenOptions, WindowScalePolicy};
|
||||||
|
use crossbeam::atomic::AtomicCell;
|
||||||
|
use nih_plug::params::persist::PersistentField;
|
||||||
|
use nih_plug::prelude::*;
|
||||||
|
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
num::NonZeroIsize,
|
||||||
|
ptr::NonNull,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use wgpu::SurfaceTargetUnsafe;
|
||||||
|
|
||||||
|
/// The time it takes for the peak meter to decay by 12 dB after switching to complete silence.
|
||||||
|
const PEAK_METER_DECAY_MS: f64 = 150.0;
|
||||||
|
|
||||||
|
pub struct CustomWgpuWindow {
|
||||||
|
gui_context: Arc<dyn GuiContext>,
|
||||||
|
|
||||||
|
device: wgpu::Device,
|
||||||
|
queue: wgpu::Queue,
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
surface: wgpu::Surface<'static>,
|
||||||
|
surface_config: wgpu::SurfaceConfiguration,
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
#[allow(unused)]
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomWgpuWindow {
|
||||||
|
fn new(
|
||||||
|
window: &mut baseview::Window<'_>,
|
||||||
|
gui_context: Arc<dyn GuiContext>,
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
scaling_factor: f32,
|
||||||
|
) -> Self {
|
||||||
|
let target = baseview_window_to_surface_target(window);
|
||||||
|
|
||||||
|
pollster::block_on(Self::create(
|
||||||
|
target,
|
||||||
|
gui_context,
|
||||||
|
params,
|
||||||
|
peak_meter,
|
||||||
|
scaling_factor,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create(
|
||||||
|
target: SurfaceTargetUnsafe,
|
||||||
|
gui_context: Arc<dyn GuiContext>,
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
scaling_factor: f32,
|
||||||
|
) -> Self {
|
||||||
|
let (unscaled_width, unscaled_height) = params.editor_state.size();
|
||||||
|
let width = (unscaled_width as f64 * scaling_factor as f64).round() as u32;
|
||||||
|
let height = (unscaled_height as f64 * scaling_factor as f64).round() as u32;
|
||||||
|
|
||||||
|
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
|
||||||
|
|
||||||
|
let surface = unsafe { instance.create_surface_unsafe(target) }.unwrap();
|
||||||
|
|
||||||
|
let adapter = instance
|
||||||
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
|
power_preference: wgpu::PowerPreference::LowPower,
|
||||||
|
force_fallback_adapter: false,
|
||||||
|
// Request an adapter which can render to our surface
|
||||||
|
compatible_surface: Some(&surface),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("Failed to find an appropriate adapter");
|
||||||
|
|
||||||
|
// Create the logical device and command queue
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(
|
||||||
|
&wgpu::DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
|
required_features: wgpu::Features::empty(),
|
||||||
|
required_limits: wgpu::Limits::downlevel_webgl2_defaults()
|
||||||
|
.using_resolution(adapter.limits()),
|
||||||
|
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create device");
|
||||||
|
|
||||||
|
const SHADER: &str = "
|
||||||
|
const VERTS = array(
|
||||||
|
vec2<f32>(0.5, 1.0),
|
||||||
|
vec2<f32>(0.0, 0.0),
|
||||||
|
vec2<f32>(1.0, 0.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
|
@location(0) position: vec2<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(
|
||||||
|
@builtin(vertex_index) in_vertex_index: u32,
|
||||||
|
) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
out.position = VERTS[in_vertex_index];
|
||||||
|
out.clip_position = vec4<f32>(out.position - 0.5, 0.0, 1.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
return vec4<f32>(in.position, 0.5, 1.0);
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(SHADER)),
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: None,
|
||||||
|
bind_group_layouts: &[],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let swapchain_capabilities = surface.get_capabilities(&adapter);
|
||||||
|
let swapchain_format = swapchain_capabilities.formats[0];
|
||||||
|
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
buffers: &[],
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("fs_main"),
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
targets: &[Some(swapchain_format.into())],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
multiview: None,
|
||||||
|
cache: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let surface_config = surface.get_default_config(&adapter, width, height).unwrap();
|
||||||
|
surface.configure(&device, &surface_config);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
gui_context,
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
pipeline,
|
||||||
|
surface,
|
||||||
|
surface_config,
|
||||||
|
params,
|
||||||
|
peak_meter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl baseview::WindowHandler for CustomWgpuWindow {
|
||||||
|
fn on_frame(&mut self, _window: &mut baseview::Window) {
|
||||||
|
// Do rendering here.
|
||||||
|
|
||||||
|
let frame = self
|
||||||
|
.surface
|
||||||
|
.get_current_texture()
|
||||||
|
.expect("Failed to acquire next swap chain texture");
|
||||||
|
let view = frame
|
||||||
|
.texture
|
||||||
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let mut encoder = self
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: None,
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: &view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
rpass.set_pipeline(&self.pipeline);
|
||||||
|
rpass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.queue.submit(Some(encoder.finish()));
|
||||||
|
frame.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
_window: &mut baseview::Window,
|
||||||
|
event: baseview::Event,
|
||||||
|
) -> baseview::EventStatus {
|
||||||
|
// Use this to set parameter values.
|
||||||
|
let _param_setter = ParamSetter::new(self.gui_context.as_ref());
|
||||||
|
|
||||||
|
match &event {
|
||||||
|
// Do event processing here.
|
||||||
|
baseview::Event::Window(event) => match event {
|
||||||
|
baseview::WindowEvent::Resized(window_info) => {
|
||||||
|
self.params.editor_state.size.store((
|
||||||
|
window_info.logical_size().width.round() as u32,
|
||||||
|
window_info.logical_size().height.round() as u32,
|
||||||
|
));
|
||||||
|
|
||||||
|
self.surface_config.width = window_info.physical_size().width;
|
||||||
|
self.surface_config.height = window_info.physical_size().height;
|
||||||
|
|
||||||
|
self.surface.configure(&self.device, &self.surface_config);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseview::EventStatus::Captured
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct CustomWgpuEditorState {
|
||||||
|
/// The window's size in logical pixels before applying `scale_factor`.
|
||||||
|
#[serde(with = "nih_plug::params::persist::serialize_atomic_cell")]
|
||||||
|
size: AtomicCell<(u32, u32)>,
|
||||||
|
/// Whether the editor's window is currently open.
|
||||||
|
#[serde(skip)]
|
||||||
|
open: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomWgpuEditorState {
|
||||||
|
pub fn from_size(size: (u32, u32)) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
size: AtomicCell::new(size),
|
||||||
|
open: AtomicBool::new(false),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a `(width, height)` pair for the current size of the GUI in logical pixels.
|
||||||
|
pub fn size(&self) -> (u32, u32) {
|
||||||
|
self.size.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the GUI is currently visible.
|
||||||
|
// Called `is_open()` instead of `open()` to avoid the ambiguity.
|
||||||
|
pub fn is_open(&self) -> bool {
|
||||||
|
self.open.load(Ordering::Acquire)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PersistentField<'a, CustomWgpuEditorState> for Arc<CustomWgpuEditorState> {
|
||||||
|
fn set(&self, new_value: CustomWgpuEditorState) {
|
||||||
|
self.size.store(new_value.size.load());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: Fn(&CustomWgpuEditorState) -> R,
|
||||||
|
{
|
||||||
|
f(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CustomWgpuEditor {
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
|
||||||
|
/// The scaling factor reported by the host, if any. On macOS this will never be set and we
|
||||||
|
/// should use the system scaling factor instead.
|
||||||
|
scaling_factor: AtomicCell<Option<f32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Editor for CustomWgpuEditor {
|
||||||
|
fn spawn(
|
||||||
|
&self,
|
||||||
|
parent: ParentWindowHandle,
|
||||||
|
context: Arc<dyn GuiContext>,
|
||||||
|
) -> Box<dyn std::any::Any + Send> {
|
||||||
|
let (unscaled_width, unscaled_height) = self.params.editor_state.size();
|
||||||
|
let scaling_factor = self.scaling_factor.load();
|
||||||
|
|
||||||
|
let gui_context = Arc::clone(&context);
|
||||||
|
|
||||||
|
let params = Arc::clone(&self.params);
|
||||||
|
let peak_meter = Arc::clone(&self.peak_meter);
|
||||||
|
|
||||||
|
let window = baseview::Window::open_parented(
|
||||||
|
&ParentWindowHandleAdapter(parent),
|
||||||
|
WindowOpenOptions {
|
||||||
|
title: String::from("WGPU Window"),
|
||||||
|
// Baseview should be doing the DPI scaling for us
|
||||||
|
size: baseview::Size::new(unscaled_width as f64, unscaled_height as f64),
|
||||||
|
// NOTE: For some reason passing 1.0 here causes the UI to be scaled on macOS but
|
||||||
|
// not the mouse events.
|
||||||
|
scale: scaling_factor
|
||||||
|
.map(|factor| WindowScalePolicy::ScaleFactor(factor as f64))
|
||||||
|
.unwrap_or(WindowScalePolicy::SystemScaleFactor),
|
||||||
|
},
|
||||||
|
move |window: &mut baseview::Window<'_>| -> CustomWgpuWindow {
|
||||||
|
CustomWgpuWindow::new(
|
||||||
|
window,
|
||||||
|
gui_context,
|
||||||
|
params,
|
||||||
|
peak_meter,
|
||||||
|
scaling_factor.unwrap_or(1.0),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
self.params.editor_state.open.store(true, Ordering::Release);
|
||||||
|
Box::new(CustomWgpuEditorHandle {
|
||||||
|
state: self.params.editor_state.clone(),
|
||||||
|
window,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> (u32, u32) {
|
||||||
|
self.params.editor_state.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_scale_factor(&self, factor: f32) -> bool {
|
||||||
|
// If the editor is currently open then the host must not change the current HiDPI scale as
|
||||||
|
// we don't have a way to handle that. Ableton Live does this.
|
||||||
|
if self.params.editor_state.is_open() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scaling_factor.store(Some(factor));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_value_changed(&self, _id: &str, _normalized_value: f32) {
|
||||||
|
// As mentioned above, for now we'll always force a redraw to allow meter widgets to work
|
||||||
|
// correctly. In the future we can use an `Arc<AtomicBool>` and only force a redraw when
|
||||||
|
// that boolean is set.
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param_modulation_changed(&self, _id: &str, _modulation_offset: f32) {}
|
||||||
|
|
||||||
|
fn param_values_changed(&self) {
|
||||||
|
// Same
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The window handle used for [`EguiEditor`].
|
||||||
|
struct CustomWgpuEditorHandle {
|
||||||
|
state: Arc<CustomWgpuEditorState>,
|
||||||
|
window: WindowHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The window handle enum stored within 'WindowHandle' contains raw pointers. Is there a way around
|
||||||
|
/// having this requirement?
|
||||||
|
unsafe impl Send for CustomWgpuEditorHandle {}
|
||||||
|
|
||||||
|
impl Drop for CustomWgpuEditorHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.state.open.store(false, Ordering::Release);
|
||||||
|
// XXX: This should automatically happen when the handle gets dropped, but apparently not
|
||||||
|
self.window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This version of `baseview` uses a different version of `raw_window_handle than NIH-plug, so we
|
||||||
|
/// need to adapt it ourselves.
|
||||||
|
struct ParentWindowHandleAdapter(nih_plug::editor::ParentWindowHandle);
|
||||||
|
|
||||||
|
unsafe impl HasRawWindowHandle for ParentWindowHandleAdapter {
|
||||||
|
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||||
|
match self.0 {
|
||||||
|
ParentWindowHandle::X11Window(window) => {
|
||||||
|
let mut handle = raw_window_handle::XcbWindowHandle::empty();
|
||||||
|
handle.window = window;
|
||||||
|
RawWindowHandle::Xcb(handle)
|
||||||
|
}
|
||||||
|
ParentWindowHandle::AppKitNsView(ns_view) => {
|
||||||
|
let mut handle = raw_window_handle::AppKitWindowHandle::empty();
|
||||||
|
handle.ns_view = ns_view;
|
||||||
|
RawWindowHandle::AppKit(handle)
|
||||||
|
}
|
||||||
|
ParentWindowHandle::Win32Hwnd(hwnd) => {
|
||||||
|
let mut handle = raw_window_handle::Win32WindowHandle::empty();
|
||||||
|
handle.hwnd = hwnd;
|
||||||
|
RawWindowHandle::Win32(handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// WGPU uses raw_window_handle v6, but baseview uses raw_window_handle v5, so manually convert it.
|
||||||
|
fn baseview_window_to_surface_target(window: &baseview::Window<'_>) -> wgpu::SurfaceTargetUnsafe {
|
||||||
|
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
||||||
|
|
||||||
|
let raw_display_handle = window.raw_display_handle();
|
||||||
|
let raw_window_handle = window.raw_window_handle();
|
||||||
|
|
||||||
|
wgpu::SurfaceTargetUnsafe::RawHandle {
|
||||||
|
raw_display_handle: match raw_display_handle {
|
||||||
|
raw_window_handle::RawDisplayHandle::AppKit(_) => {
|
||||||
|
raw_window_handle_06::RawDisplayHandle::AppKit(
|
||||||
|
raw_window_handle_06::AppKitDisplayHandle::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawDisplayHandle::Xlib(handle) => {
|
||||||
|
raw_window_handle_06::RawDisplayHandle::Xlib(
|
||||||
|
raw_window_handle_06::XlibDisplayHandle::new(
|
||||||
|
NonNull::new(handle.display),
|
||||||
|
handle.screen,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawDisplayHandle::Xcb(handle) => {
|
||||||
|
raw_window_handle_06::RawDisplayHandle::Xcb(
|
||||||
|
raw_window_handle_06::XcbDisplayHandle::new(
|
||||||
|
NonNull::new(handle.connection),
|
||||||
|
handle.screen,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawDisplayHandle::Windows(_) => {
|
||||||
|
raw_window_handle_06::RawDisplayHandle::Windows(
|
||||||
|
raw_window_handle_06::WindowsDisplayHandle::new(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
},
|
||||||
|
raw_window_handle: match raw_window_handle {
|
||||||
|
raw_window_handle::RawWindowHandle::AppKit(handle) => {
|
||||||
|
raw_window_handle_06::RawWindowHandle::AppKit(
|
||||||
|
raw_window_handle_06::AppKitWindowHandle::new(
|
||||||
|
NonNull::new(handle.ns_view).unwrap(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawWindowHandle::Xlib(handle) => {
|
||||||
|
raw_window_handle_06::RawWindowHandle::Xlib(
|
||||||
|
raw_window_handle_06::XlibWindowHandle::new(handle.window),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawWindowHandle::Xcb(handle) => {
|
||||||
|
raw_window_handle_06::RawWindowHandle::Xcb(
|
||||||
|
raw_window_handle_06::XcbWindowHandle::new(
|
||||||
|
NonZeroU32::new(handle.window).unwrap(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
raw_window_handle::RawWindowHandle::Win32(handle) => {
|
||||||
|
// will this work? i have no idea!
|
||||||
|
let mut raw_handle = raw_window_handle_06::Win32WindowHandle::new(
|
||||||
|
NonZeroIsize::new(handle.hwnd as isize).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
raw_handle.hinstance = handle
|
||||||
|
.hinstance
|
||||||
|
.is_null()
|
||||||
|
.then(|| NonZeroIsize::new(handle.hinstance as isize).unwrap());
|
||||||
|
|
||||||
|
raw_window_handle_06::RawWindowHandle::Win32(raw_handle)
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is mostly identical to the gain example, minus some fluff, and with a GUI.
|
||||||
|
pub struct MyPlugin {
|
||||||
|
params: Arc<MyPluginParams>,
|
||||||
|
|
||||||
|
/// Needed to normalize the peak meter's response based on the sample rate.
|
||||||
|
peak_meter_decay_weight: f32,
|
||||||
|
/// The current data for the peak meter. This is stored as an [`Arc`] so we can share it between
|
||||||
|
/// the GUI and the audio processing parts. If you have more state to share, then it's a good
|
||||||
|
/// idea to put all of that in a struct behind a single `Arc`.
|
||||||
|
///
|
||||||
|
/// This is stored as voltage gain.
|
||||||
|
peak_meter: Arc<AtomicF32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Params)]
|
||||||
|
pub struct MyPluginParams {
|
||||||
|
/// The editor state, saved together with the parameter state so the custom scaling can be
|
||||||
|
/// restored.
|
||||||
|
#[persist = "editor-state"]
|
||||||
|
editor_state: Arc<CustomWgpuEditorState>,
|
||||||
|
|
||||||
|
#[id = "gain"]
|
||||||
|
pub gain: FloatParam,
|
||||||
|
|
||||||
|
#[id = "foobar"]
|
||||||
|
pub some_int: IntParam,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MyPlugin {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
params: Arc::new(MyPluginParams::default()),
|
||||||
|
|
||||||
|
peak_meter_decay_weight: 1.0,
|
||||||
|
peak_meter: Arc::new(AtomicF32::new(util::MINUS_INFINITY_DB)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MyPluginParams {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
editor_state: CustomWgpuEditorState::from_size((400, 300)),
|
||||||
|
|
||||||
|
// See the main gain example for more details
|
||||||
|
gain: FloatParam::new(
|
||||||
|
"Gain",
|
||||||
|
util::db_to_gain(0.0),
|
||||||
|
FloatRange::Skewed {
|
||||||
|
min: util::db_to_gain(-30.0),
|
||||||
|
max: util::db_to_gain(30.0),
|
||||||
|
factor: FloatRange::gain_skew_factor(-30.0, 30.0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_smoother(SmoothingStyle::Logarithmic(50.0))
|
||||||
|
.with_unit(" dB")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_gain_to_db(2))
|
||||||
|
.with_string_to_value(formatters::s2v_f32_gain_to_db()),
|
||||||
|
some_int: IntParam::new("Something", 3, IntRange::Linear { min: 0, max: 3 }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for MyPlugin {
|
||||||
|
const NAME: &'static str = "BYO GUI Example (WGPU)";
|
||||||
|
const VENDOR: &'static str = "Moist Plugins GmbH";
|
||||||
|
const URL: &'static str = "https://youtu.be/dQw4w9WgXcQ";
|
||||||
|
const EMAIL: &'static str = "info@example.com";
|
||||||
|
|
||||||
|
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
|
||||||
|
AudioIOLayout {
|
||||||
|
main_input_channels: NonZeroU32::new(2),
|
||||||
|
main_output_channels: NonZeroU32::new(2),
|
||||||
|
..AudioIOLayout::const_default()
|
||||||
|
},
|
||||||
|
AudioIOLayout {
|
||||||
|
main_input_channels: NonZeroU32::new(1),
|
||||||
|
main_output_channels: NonZeroU32::new(1),
|
||||||
|
..AudioIOLayout::const_default()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const SAMPLE_ACCURATE_AUTOMATION: bool = true;
|
||||||
|
|
||||||
|
type SysExMessage = ();
|
||||||
|
type BackgroundTask = ();
|
||||||
|
|
||||||
|
fn params(&self) -> Arc<dyn Params> {
|
||||||
|
self.params.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
|
||||||
|
Some(Box::new(CustomWgpuEditor {
|
||||||
|
params: Arc::clone(&self.params),
|
||||||
|
peak_meter: Arc::clone(&self.peak_meter),
|
||||||
|
|
||||||
|
// TODO: We can't get the size of the window when baseview does its own scaling, so if the
|
||||||
|
// host does not set a scale factor on Windows or Linux we should just use a factor of
|
||||||
|
// 1. That may make the GUI tiny but it also prevents it from getting cut off.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
scaling_factor: AtomicCell::new(None),
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
scaling_factor: AtomicCell::new(Some(1.0)),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize(
|
||||||
|
&mut self,
|
||||||
|
_audio_io_layout: &AudioIOLayout,
|
||||||
|
buffer_config: &BufferConfig,
|
||||||
|
_context: &mut impl InitContext<Self>,
|
||||||
|
) -> bool {
|
||||||
|
// TODO: Figure out a way to disable log spam from wgpu.
|
||||||
|
|
||||||
|
// After `PEAK_METER_DECAY_MS` milliseconds of pure silence, the peak meter's value should
|
||||||
|
// have dropped by 12 dB
|
||||||
|
self.peak_meter_decay_weight = 0.25f64
|
||||||
|
.powf((buffer_config.sample_rate as f64 * PEAK_METER_DECAY_MS / 1000.0).recip())
|
||||||
|
as f32;
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(
|
||||||
|
&mut self,
|
||||||
|
buffer: &mut Buffer,
|
||||||
|
_aux: &mut AuxiliaryBuffers,
|
||||||
|
_context: &mut impl ProcessContext<Self>,
|
||||||
|
) -> ProcessStatus {
|
||||||
|
for channel_samples in buffer.iter_samples() {
|
||||||
|
let mut amplitude = 0.0;
|
||||||
|
let num_samples = channel_samples.len();
|
||||||
|
|
||||||
|
let gain = self.params.gain.smoothed.next();
|
||||||
|
for sample in channel_samples {
|
||||||
|
*sample *= gain;
|
||||||
|
amplitude += *sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To save resources, a plugin can (and probably should!) only perform expensive
|
||||||
|
// calculations that are only displayed on the GUI while the GUI is open
|
||||||
|
if self.params.editor_state.is_open() {
|
||||||
|
amplitude = (amplitude / num_samples as f32).abs();
|
||||||
|
let current_peak_meter = self.peak_meter.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let new_peak_meter = if amplitude > current_peak_meter {
|
||||||
|
amplitude
|
||||||
|
} else {
|
||||||
|
current_peak_meter * self.peak_meter_decay_weight
|
||||||
|
+ amplitude * (1.0 - self.peak_meter_decay_weight)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.peak_meter
|
||||||
|
.store(new_peak_meter, std::sync::atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessStatus::Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClapPlugin for MyPlugin {
|
||||||
|
const CLAP_ID: &'static str = "com.moist-plugins-gmbh.byo-gui-wgpu";
|
||||||
|
const CLAP_DESCRIPTION: Option<&'static str> =
|
||||||
|
Some("A simple example plugin with a raw WGPU context for rendering");
|
||||||
|
const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL);
|
||||||
|
const CLAP_SUPPORT_URL: Option<&'static str> = None;
|
||||||
|
const CLAP_FEATURES: &'static [ClapFeature] = &[
|
||||||
|
ClapFeature::AudioEffect,
|
||||||
|
ClapFeature::Stereo,
|
||||||
|
ClapFeature::Mono,
|
||||||
|
ClapFeature::Utility,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vst3Plugin for MyPlugin {
|
||||||
|
const VST3_CLASS_ID: [u8; 16] = *b"ByoGuiWGPUWooooo";
|
||||||
|
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
|
||||||
|
&[Vst3SubCategory::Fx, Vst3SubCategory::Tools];
|
||||||
|
}
|
||||||
|
|
||||||
|
nih_export_clap!(MyPlugin);
|
||||||
|
nih_export_vst3!(MyPlugin);
|
||||||
7
plugins/examples/byo_gui_wgpu/src/main.rs
Normal file
7
plugins/examples/byo_gui_wgpu/src/main.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
use nih_plug::prelude::*;
|
||||||
|
|
||||||
|
use byo_gui_wgpu::MyPlugin;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
nih_export_standalone::<MyPlugin>();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user