Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e151ab4f26 | ||
|
|
e28f61447f | ||
|
|
388552f1c4 | ||
|
|
e68e73d3d5 | ||
|
|
a5669805c8 | ||
|
|
cef7336517 | ||
|
|
50a3c1a1e5 | ||
|
|
684f5ce866 | ||
|
|
80da40f678 | ||
|
|
3cca2f41f4 | ||
|
|
6511d9d007 |
@@ -7,7 +7,25 @@ This template should help get you started developing with Tauri, React and Types
|
|||||||
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- Move meters polling logic outside dsp state because it causes re-render of everything tied to dsp constantly
|
- Move meters polling logic outside dsp state because it causes re-render of everything tied to dsp constantly
|
||||||
|
|
||||||
- Check render to improve perfoormances
|
- Check render to improve perfoormances
|
||||||
|
|
||||||
- Check end to end behavioour
|
- Check in meter if clip bool is limit instead
|
||||||
|
|
||||||
|
- On close disconenct all ds^p
|
||||||
|
|
||||||
|
- Check end to end behavioour
|
||||||
|
|
||||||
|
- Roll back on scroll when 2 scroll
|
||||||
|
|
||||||
|
- test en dto end connection
|
||||||
|
|
||||||
|
Data: 10020001052f 31313131 10032a // lock
|
||||||
|
|
||||||
|
Data: 10020001052d 31313131 100328 // unlock fail
|
||||||
|
Data: 10020100032d 3100 10031f
|
||||||
|
|
||||||
|
Data: 10020001062d 0031313131 10032b // unlock success
|
||||||
|
Data: 10020100032d 0001 10032f
|
||||||
Generated
+185
-39
@@ -175,7 +175,7 @@ checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.2",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -225,6 +225,12 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.23.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -397,7 +403,7 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -453,6 +459,17 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures 0.3.1",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.45"
|
version = "0.4.45"
|
||||||
@@ -543,6 +560,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -618,9 +644,9 @@ checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.23.0"
|
version = "0.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"darling_macro",
|
"darling_macro",
|
||||||
@@ -628,26 +654,26 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_core"
|
name = "darling_core"
|
||||||
version = "0.23.0"
|
version = "0.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
|
checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 2.0.119",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.23.0"
|
version = "0.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -661,6 +687,37 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"defmt-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt-macros"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
|
||||||
|
dependencies = [
|
||||||
|
"defmt-parser",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "defmt-parser"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror 2.0.21",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.5.8"
|
version = "0.5.8"
|
||||||
@@ -796,12 +853,13 @@ dependencies = [
|
|||||||
name = "dsp_thomann"
|
name = "dsp_thomann"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1255,6 +1313,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi 6.0.0",
|
"r-efi 6.0.0",
|
||||||
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1762,6 +1821,60 @@ dependencies = [
|
|||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff"
|
||||||
|
version = "0.2.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb"
|
||||||
|
dependencies = [
|
||||||
|
"defmt",
|
||||||
|
"jiff-core",
|
||||||
|
"jiff-static",
|
||||||
|
"jiff-tzdb-platform",
|
||||||
|
"log",
|
||||||
|
"portable-atomic",
|
||||||
|
"portable-atomic-util",
|
||||||
|
"serde_core",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-core"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c"
|
||||||
|
dependencies = [
|
||||||
|
"defmt",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-static"
|
||||||
|
version = "0.2.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-core",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-tzdb"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-tzdb-platform"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-tzdb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni"
|
name = "jni"
|
||||||
version = "0.21.1"
|
version = "0.21.1"
|
||||||
@@ -2005,7 +2118,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"png 0.18.1",
|
"png 0.18.1",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2493,6 +2606,21 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic-util"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -2606,6 +2734,23 @@ version = "6.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
|
||||||
|
dependencies = [
|
||||||
|
"chacha20",
|
||||||
|
"getrandom 0.4.3",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raw-window-handle"
|
name = "raw-window-handle"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -2629,7 +2774,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.17",
|
"getrandom 0.2.17",
|
||||||
"libredox",
|
"libredox",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2649,7 +2794,7 @@ checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.2",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2883,7 +3028,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.2",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2918,7 +3063,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.2",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2941,16 +3086,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "3.21.0"
|
version = "3.24.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c"
|
checksum = "df9adc193c780ef8f159aee8b61e2d5801aaa555e6eb0947fe45530ec506296f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.23.1",
|
||||||
"bs58",
|
"bs58",
|
||||||
"chrono",
|
"chrono",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"indexmap 2.14.0",
|
"indexmap 2.14.0",
|
||||||
|
"jiff",
|
||||||
"schemars 0.9.0",
|
"schemars 0.9.0",
|
||||||
"schemars 1.2.1",
|
"schemars 1.2.1",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@@ -2961,14 +3107,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with_macros"
|
name = "serde_with_macros"
|
||||||
version = "3.21.0"
|
version = "3.24.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
|
checksum = "3e17bbc68e28663bbbb90df47e058aa7eda4fb445b89fe70457bb94fbccf6e49"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3009,7 +3155,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures 0.2.17",
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3199,9 +3345,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "3.0.2"
|
version = "3.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3"
|
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3339,7 +3485,7 @@ dependencies = [
|
|||||||
"tauri-runtime",
|
"tauri-runtime",
|
||||||
"tauri-runtime-wry",
|
"tauri-runtime-wry",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tray-icon",
|
"tray-icon",
|
||||||
"url",
|
"url",
|
||||||
@@ -3390,7 +3536,7 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"time",
|
"time",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -3443,7 +3589,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"url",
|
"url",
|
||||||
"windows",
|
"windows",
|
||||||
"zbus",
|
"zbus",
|
||||||
@@ -3467,7 +3613,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"url",
|
"url",
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webview2-com",
|
"webview2-com",
|
||||||
@@ -3530,7 +3676,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"swift-rs",
|
"swift-rs",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"toml 1.1.3+spec-1.1.0",
|
"toml 1.1.3+spec-1.1.0",
|
||||||
"url",
|
"url",
|
||||||
"urlpattern",
|
"urlpattern",
|
||||||
@@ -3582,11 +3728,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.20"
|
version = "2.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 2.0.20",
|
"thiserror-impl 2.0.21",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3602,13 +3748,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "2.0.20"
|
version = "2.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.2",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3919,7 +4065,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"png 0.18.1",
|
"png 0.18.1",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4279,7 +4425,7 @@ version = "0.38.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
|
checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"windows",
|
"windows",
|
||||||
"windows-core 0.61.2",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
@@ -4714,7 +4860,7 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
"soup3",
|
"soup3",
|
||||||
"tao-macros",
|
"tao-macros",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.21",
|
||||||
"url",
|
"url",
|
||||||
"webkit2gtk",
|
"webkit2gtk",
|
||||||
"webkit2gtk-sys",
|
"webkit2gtk-sys",
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ pub struct DSP408Snapshot {
|
|||||||
pub state: types::DSPState,
|
pub state: types::DSPState,
|
||||||
pub input_meters: types::InputMeters,
|
pub input_meters: types::InputMeters,
|
||||||
pub output_meters: types::OutputMeters,
|
pub output_meters: types::OutputMeters,
|
||||||
pub mode: types::DSPMode,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -104,7 +103,6 @@ pub async fn get_dsp_snapshot(
|
|||||||
state: device.dsp.state().clone(),
|
state: device.dsp.state().clone(),
|
||||||
input_meters: device.dsp.input_meters().clone(),
|
input_meters: device.dsp.input_meters().clone(),
|
||||||
output_meters: device.dsp.output_meters().clone(),
|
output_meters: device.dsp.output_meters().clone(),
|
||||||
mode: device.dsp.mode(),
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { DSP, AppPage } from './types';
|
import { DSP, AppPage, isConnected } from './types';
|
||||||
import TopBar from './components/Topbar/Topbar';
|
import TopBar from './components/Topbar/Topbar';
|
||||||
import DSPPage from './pages/DspPage/DspPage';
|
import DSPPage from './pages/DspPage/DspPage';
|
||||||
import AddDSPForm from './pages/AddDspPage/AddDspPage';
|
import AddDSPForm from './pages/AddDspPage/AddDspPage';
|
||||||
@@ -17,7 +17,7 @@ function App() {
|
|||||||
const [selected, setSelected] = useState<number | null>(null);
|
const [selected, setSelected] = useState<number | null>(null);
|
||||||
const [page, setPage] = useState<AppPage>('home');
|
const [page, setPage] = useState<AppPage>('home');
|
||||||
const { notifications, notify, removeNotification, hoverNotification } = useNotifications();
|
const { notifications, notify, removeNotification, hoverNotification } = useNotifications();
|
||||||
const connectedDspCount = dsps.filter((dsp) => dsp.status === 'connected').length;
|
const connectedDspCount = dsps.filter((dsp) => isConnected(dsp)).length;
|
||||||
|
|
||||||
const activeDsp = dsps.find((d) => d.id === selected) ?? null;
|
const activeDsp = dsps.find((d) => d.id === selected) ?? null;
|
||||||
const activeInstance = activeDsp ? createDSPInstance(activeDsp, setDsps, notify) : null;
|
const activeInstance = activeDsp ? createDSPInstance(activeDsp, setDsps, notify) : null;
|
||||||
|
|||||||
@@ -12,6 +12,23 @@ const PENDING_FALLBACK_MS = 1000;
|
|||||||
|
|
||||||
type CommitResult = Promise<boolean | void> | boolean | void;
|
type CommitResult = Promise<boolean | void> | boolean | void;
|
||||||
|
|
||||||
|
function useNonPassiveWheel(
|
||||||
|
ref: React.RefObject<HTMLElement | null>,
|
||||||
|
handler: (e: WheelEvent) => void
|
||||||
|
) {
|
||||||
|
const handlerRef = useRef(handler);
|
||||||
|
handlerRef.current = handler;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const el = ref.current;
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
const listener = (e: WheelEvent) => handlerRef.current(e);
|
||||||
|
el.addEventListener('wheel', listener, { passive: false });
|
||||||
|
return () => el.removeEventListener('wheel', listener);
|
||||||
|
}, [ref]);
|
||||||
|
}
|
||||||
|
|
||||||
function StepperArrows({
|
function StepperArrows({
|
||||||
onUp,
|
onUp,
|
||||||
onDown,
|
onDown,
|
||||||
@@ -80,6 +97,17 @@ function ChoiceStepperList<T>({
|
|||||||
const pendingValue = useRef<T | null>(null);
|
const pendingValue = useRef<T | null>(null);
|
||||||
const wheelTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const wheelTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const fallbackTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const fallbackTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
useNonPassiveWheel(inputRef, (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (e.deltaY < 0 && canUp) {
|
||||||
|
stepWheel(1);
|
||||||
|
} else if (e.deltaY > 0 && canDown) {
|
||||||
|
stepWheel(-1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (interacting.current) {
|
if (interacting.current) {
|
||||||
@@ -180,21 +208,13 @@ function ChoiceStepperList<T>({
|
|||||||
return (
|
return (
|
||||||
<div className={`choice-stepper ${className ?? ''}`} data-disabled={disabled || undefined}>
|
<div className={`choice-stepper ${className ?? ''}`} data-disabled={disabled || undefined}>
|
||||||
<input
|
<input
|
||||||
|
ref={inputRef}
|
||||||
id={id}
|
id={id}
|
||||||
className="choice-stepper__input"
|
className="choice-stepper__input"
|
||||||
value={text}
|
value={text}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(e) => setText(e.target.value)}
|
onChange={(e) => setText(e.target.value)}
|
||||||
onBlur={commitText}
|
onBlur={commitText}
|
||||||
onWheel={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (e.deltaY < 0 && canUp) {
|
|
||||||
stepWheel(1);
|
|
||||||
} else if (e.deltaY > 0 && canDown) {
|
|
||||||
stepWheel(-1);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.currentTarget.blur();
|
e.currentTarget.blur();
|
||||||
@@ -256,6 +276,17 @@ function ChoiceStepperRange({
|
|||||||
const pendingValue = useRef<number | null>(null);
|
const pendingValue = useRef<number | null>(null);
|
||||||
const wheelTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const wheelTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const fallbackTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const fallbackTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
useNonPassiveWheel(inputRef, (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (e.deltaY < 0 && canUp) {
|
||||||
|
stepWheel(1);
|
||||||
|
} else if (e.deltaY > 0 && canDown) {
|
||||||
|
stepWheel(-1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (interacting.current) {
|
if (interacting.current) {
|
||||||
@@ -344,21 +375,13 @@ function ChoiceStepperRange({
|
|||||||
return (
|
return (
|
||||||
<div className={`choice-stepper ${className ?? ''}`} data-disabled={disabled || undefined}>
|
<div className={`choice-stepper ${className ?? ''}`} data-disabled={disabled || undefined}>
|
||||||
<input
|
<input
|
||||||
|
ref={inputRef}
|
||||||
id={id}
|
id={id}
|
||||||
className="choice-stepper__input"
|
className="choice-stepper__input"
|
||||||
value={text}
|
value={text}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(e) => setText(e.target.value)}
|
onChange={(e) => setText(e.target.value)}
|
||||||
onBlur={commitText}
|
onBlur={commitText}
|
||||||
onWheel={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (e.deltaY < 0 && canUp) {
|
|
||||||
stepWheel(1);
|
|
||||||
} else if (e.deltaY > 0 && canDown) {
|
|
||||||
stepWheel(-1);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
e.currentTarget.blur();
|
e.currentTarget.blur();
|
||||||
|
|||||||
@@ -92,6 +92,23 @@ function formatGainDb(value: number) {
|
|||||||
return `${sign}${value.toFixed(1)} dB`;
|
return `${sign}${value.toFixed(1)} dB`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useNonPassiveWheel(
|
||||||
|
ref: React.RefObject<Element | null>,
|
||||||
|
handler: (e: WheelEvent) => void
|
||||||
|
) {
|
||||||
|
const handlerRef = useRef(handler);
|
||||||
|
handlerRef.current = handler;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const el = ref.current;
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
const listener = (e: Event) => handlerRef.current(e as WheelEvent);
|
||||||
|
el.addEventListener('wheel', listener, { passive: false });
|
||||||
|
return () => el.removeEventListener('wheel', listener);
|
||||||
|
}, [ref]);
|
||||||
|
}
|
||||||
|
|
||||||
type GainTooltipState = {
|
type GainTooltipState = {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
x: number;
|
x: number;
|
||||||
@@ -104,11 +121,13 @@ function SignalFlowMatrix({
|
|||||||
setMatrixRouting,
|
setMatrixRouting,
|
||||||
setMatrixGain,
|
setMatrixGain,
|
||||||
setActiveSidebar,
|
setActiveSidebar,
|
||||||
|
onGainPreview,
|
||||||
}: {
|
}: {
|
||||||
dsp: DSP408;
|
dsp: DSP408;
|
||||||
setMatrixRouting: (output: OutputChannel, inputs: InputChannel[]) => Promise<boolean>;
|
setMatrixRouting: (output: OutputChannel, inputs: InputChannel[]) => Promise<boolean>;
|
||||||
setMatrixGain: (input: InputChannel, output: OutputChannel, gain: number) => Promise<boolean>;
|
setMatrixGain: (input: InputChannel, output: OutputChannel, gain: number) => Promise<boolean>;
|
||||||
setActiveSidebar: React.Dispatch<React.SetStateAction<SidebarId>>;
|
setActiveSidebar: React.Dispatch<React.SetStateAction<SidebarId>>;
|
||||||
|
onGainPreview: (input: InputChannel, output: OutputChannel, value: number | null) => void;
|
||||||
}) {
|
}) {
|
||||||
const nInputs = INPUT_ORDER.length;
|
const nInputs = INPUT_ORDER.length;
|
||||||
const nOutputs = OUTPUT_ORDER.length;
|
const nOutputs = OUTPUT_ORDER.length;
|
||||||
@@ -137,9 +156,16 @@ function SignalFlowMatrix({
|
|||||||
});
|
});
|
||||||
const hideTooltipTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const hideTooltipTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
|
const WHEEL_COMMIT_DELAY = 250;
|
||||||
|
|
||||||
|
// key = `${input}-${output}`
|
||||||
|
const pendingGains = useRef<Map<string, number>>(new Map());
|
||||||
|
const commitTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (hideTooltipTimer.current) clearTimeout(hideTooltipTimer.current);
|
if (hideTooltipTimer.current) clearTimeout(hideTooltipTimer.current);
|
||||||
|
commitTimers.current.forEach(clearTimeout);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -151,28 +177,55 @@ function SignalFlowMatrix({
|
|||||||
void setMatrixRouting(output, next);
|
void setMatrixRouting(output, next);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleWheelGain = (e: React.WheelEvent, output: OutputChannel, input: InputChannel) => {
|
const svgRef = useRef<SVGSVGElement>(null);
|
||||||
|
|
||||||
|
const handleWheelGain = (e: WheelEvent, output: OutputChannel, input: InputChannel) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const current = getGain(dsp, output, input);
|
|
||||||
|
const key = `${input}-${output}`;
|
||||||
|
// Base on the pending value if we're mid-wheel, otherwise the real state
|
||||||
|
const current = pendingGains.current.get(key) ?? getGain(dsp, output, input);
|
||||||
const delta = e.deltaY < 0 ? MATRIX_GAIN_STEP : -MATRIX_GAIN_STEP;
|
const delta = e.deltaY < 0 ? MATRIX_GAIN_STEP : -MATRIX_GAIN_STEP;
|
||||||
const next =
|
const next =
|
||||||
Math.round(Math.min(MATRIX_GAIN_MAX, Math.max(MATRIX_GAIN_MIN, current + delta)) * 10) / 10;
|
Math.round(Math.min(MATRIX_GAIN_MAX, Math.max(MATRIX_GAIN_MIN, current + delta)) * 10) / 10;
|
||||||
void setMatrixGain(input, output, next);
|
|
||||||
|
|
||||||
// Show a small tooltip with the new gain value right next to the cursor,
|
pendingGains.current.set(key, next);
|
||||||
// then let it fade away shortly after wheeling stops.
|
onGainPreview(input, output, next);
|
||||||
|
|
||||||
|
// Debounced send: only the final value goes out once the wheel pauses
|
||||||
|
const existing = commitTimers.current.get(key);
|
||||||
|
if (existing) clearTimeout(existing);
|
||||||
|
commitTimers.current.set(
|
||||||
|
key,
|
||||||
|
setTimeout(async () => {
|
||||||
|
commitTimers.current.delete(key);
|
||||||
|
const value = pendingGains.current.get(key);
|
||||||
|
if (value === undefined) return;
|
||||||
|
await setMatrixGain(input, output, value);
|
||||||
|
// Only release if the user hasn't wheeled again while we were awaiting
|
||||||
|
if (pendingGains.current.get(key) === value) {
|
||||||
|
pendingGains.current.delete(key);
|
||||||
|
onGainPreview(input, output, null);
|
||||||
|
}
|
||||||
|
}, WHEEL_COMMIT_DELAY)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tooltip (unchanged, shows the pending value)
|
||||||
if (hideTooltipTimer.current) clearTimeout(hideTooltipTimer.current);
|
if (hideTooltipTimer.current) clearTimeout(hideTooltipTimer.current);
|
||||||
setGainTooltip({
|
setGainTooltip({ visible: true, x: e.clientX, y: e.clientY, label: formatGainDb(next) });
|
||||||
visible: true,
|
|
||||||
x: e.clientX,
|
|
||||||
y: e.clientY,
|
|
||||||
label: formatGainDb(next),
|
|
||||||
});
|
|
||||||
hideTooltipTimer.current = setTimeout(() => {
|
hideTooltipTimer.current = setTimeout(() => {
|
||||||
setGainTooltip((t) => ({ ...t, visible: false }));
|
setGainTooltip((t) => ({ ...t, visible: false }));
|
||||||
}, GAIN_TOOLTIP_HOLD_MS);
|
}, GAIN_TOOLTIP_HOLD_MS);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useNonPassiveWheel(svgRef, (e) => {
|
||||||
|
const hit = (e.target as Element).closest<SVGElement>('[data-wheel-route]');
|
||||||
|
if (!hit) return; // not over a connected line: let the page scroll normally
|
||||||
|
const { input, output } = hit.dataset;
|
||||||
|
if (!input || !output) return;
|
||||||
|
handleWheelGain(e, output as OutputChannel, input as InputChannel);
|
||||||
|
});
|
||||||
|
|
||||||
const routePairs = useMemo(() => {
|
const routePairs = useMemo(() => {
|
||||||
const pairs: { input: InputChannel; output: OutputChannel; i: number; j: number }[] = [];
|
const pairs: { input: InputChannel; output: OutputChannel; i: number; j: number }[] = [];
|
||||||
OUTPUT_ORDER.forEach((output, j) => {
|
OUTPUT_ORDER.forEach((output, j) => {
|
||||||
@@ -196,6 +249,7 @@ function SignalFlowMatrix({
|
|||||||
return (
|
return (
|
||||||
<div className="signal-flow">
|
<div className="signal-flow">
|
||||||
<svg
|
<svg
|
||||||
|
ref={svgRef}
|
||||||
viewBox={`0 0 ${W} ${H}`}
|
viewBox={`0 0 ${W} ${H}`}
|
||||||
preserveAspectRatio="xMidYMid meet"
|
preserveAspectRatio="xMidYMid meet"
|
||||||
className="signal-flow__svg"
|
className="signal-flow__svg"
|
||||||
@@ -221,7 +275,9 @@ function SignalFlowMatrix({
|
|||||||
strokeWidth={LINE_HITBOX_WIDTH}
|
strokeWidth={LINE_HITBOX_WIDTH}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={() => handleToggle(output, input)}
|
onClick={() => handleToggle(output, input)}
|
||||||
onWheel={connected ? (e) => handleWheelGain(e, output, input) : undefined}
|
data-wheel-route={connected ? '' : undefined}
|
||||||
|
data-input={input}
|
||||||
|
data-output={output}
|
||||||
/>
|
/>
|
||||||
{/* Visible line, purely decorative — no pointer events, so it never
|
{/* Visible line, purely decorative — no pointer events, so it never
|
||||||
competes with the hitbox above for hover/click/wheel. */}
|
competes with the hitbox above for hover/click/wheel. */}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { DSP, AppPage } from '../../types';
|
import { DSP, AppPage, isConnected } from '../../types';
|
||||||
import AnimatedLogo from '../../assets/icons/AnimatedLogo';
|
import AnimatedLogo from '../../assets/icons/AnimatedLogo';
|
||||||
import { PlusIcon } from '../../assets/icons/PlusIcon';
|
import { PlusIcon } from '../../assets/icons/PlusIcon';
|
||||||
import { CrownIcon } from '../../assets/icons/CrownIcon';
|
import { CrownIcon } from '../../assets/icons/CrownIcon';
|
||||||
@@ -36,7 +36,7 @@ function Tab({
|
|||||||
onDisconnect: (dsp: DSP) => void;
|
onDisconnect: (dsp: DSP) => void;
|
||||||
actions?: React.ReactNode;
|
actions?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const isConnected = dsp.status === 'connected';
|
const isDspConnected = isConnected(dsp);
|
||||||
|
|
||||||
const handleTabKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
const handleTabKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
@@ -60,18 +60,28 @@ function Tab({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="tab-led"
|
className="tab-led"
|
||||||
aria-label={isConnected ? `Disconnect ${dsp.name}` : `Connect ${dsp.name}`}
|
aria-label={isDspConnected ? `Disconnect ${dsp.name}` : `Connect ${dsp.name}`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (isConnected) {
|
onSelect();
|
||||||
|
|
||||||
|
if (isDspConnected) {
|
||||||
onDisconnect(dsp);
|
onDisconnect(dsp);
|
||||||
} else {
|
} else {
|
||||||
onConnect(dsp);
|
onConnect(dsp);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Led status={dsp.status} />
|
<Led
|
||||||
|
status={
|
||||||
|
dsp.isConnecting
|
||||||
|
? 'connecting'
|
||||||
|
: isConnected(dsp)
|
||||||
|
? 'connected'
|
||||||
|
: 'disconnected'
|
||||||
|
}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span className="tab-name" title={dsp.name}>
|
<span className="tab-name" title={dsp.name}>
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ import { getPeqCurvePoints, getPeqPhasePoints } from '../../utils/graph/peq';
|
|||||||
import { logSpace, sumCurvePoints, sumPhaseCurvePoints } from '../../utils/graph/curve';
|
import { logSpace, sumCurvePoints, sumPhaseCurvePoints } from '../../utils/graph/curve';
|
||||||
import { getCrossoverCurvePoints, getCrossoverPhasePoints } from '../../utils/graph/crossover';
|
import { getCrossoverCurvePoints, getCrossoverPhasePoints } from '../../utils/graph/crossover';
|
||||||
|
|
||||||
|
function nearestQValue(q: number, values: readonly number[] = PEAK_Q_VALUES): number {
|
||||||
|
return values.reduce((nearest, current) =>
|
||||||
|
Math.abs(current - q) < Math.abs(nearest - q) ? current : nearest
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type GraphView = 'both' | 'magnitude' | 'phase';
|
type GraphView = 'both' | 'magnitude' | 'phase';
|
||||||
|
|
||||||
function GraphViewToggle({
|
function GraphViewToggle({
|
||||||
@@ -133,9 +139,11 @@ function PeqPanelSliders({
|
|||||||
const qDisabled = PEQ_Q_UNUSED.has(peq.filter_type);
|
const qDisabled = PEQ_Q_UNUSED.has(peq.filter_type);
|
||||||
const gainDisabled = !PEQ_GAIN_USED.has(peq.filter_type);
|
const gainDisabled = !PEQ_GAIN_USED.has(peq.filter_type);
|
||||||
|
|
||||||
|
const nearestQ = nearestQValue(peq.q);
|
||||||
|
|
||||||
const commit = (overrides: Partial<PEQ>) => {
|
const commit = (overrides: Partial<PEQ>) => {
|
||||||
const next = { ...peq, ...overrides };
|
const next = { ...peq, ...overrides };
|
||||||
return setPEQ(next.gain, next.frequency, next.q, next.filter_type, next.bypass);
|
return setPEQ(next.gain, next.frequency, overrides.q ?? nearestQ, next.filter_type, next.bypass);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -169,9 +177,9 @@ function PeqPanelSliders({
|
|||||||
max={qMax}
|
max={qMax}
|
||||||
values={PEAK_Q_VALUES}
|
values={PEAK_Q_VALUES}
|
||||||
formatValue={(v) => v.toFixed(2)}
|
formatValue={(v) => v.toFixed(2)}
|
||||||
value={peq.q}
|
value={nearestQ}
|
||||||
onChange={(value) => commit({ q: value })}
|
onChange={(value) => commit({ q: value })}
|
||||||
onInput={(value) => onLiveValue({ ...peq, q: value })}
|
onInput={(value) => onLiveValue({ ...peq, q: nearestQValue(value) })}
|
||||||
disabled={qDisabled}
|
disabled={qDisabled}
|
||||||
style={{ width: '100%', flex: '1 1 auto' }}
|
style={{ width: '100%', flex: '1 1 auto' }}
|
||||||
/>
|
/>
|
||||||
@@ -318,9 +326,16 @@ function PeqBandList({
|
|||||||
bandsOrder: PEQBand[];
|
bandsOrder: PEQBand[];
|
||||||
}) {
|
}) {
|
||||||
const commit = (band: PEQBand, overrides: Partial<PEQ>) => {
|
const commit = (band: PEQBand, overrides: Partial<PEQ>) => {
|
||||||
const peq = liveBands[band] ?? bands[band];
|
const live = liveBands[band] ?? bands[band];
|
||||||
const next = { ...peq, ...overrides };
|
const next = { ...live, ...overrides };
|
||||||
return setPEQ(band, next.gain, next.frequency, next.q, next.filter_type, next.bypass);
|
return setPEQ(
|
||||||
|
band,
|
||||||
|
next.gain,
|
||||||
|
next.frequency,
|
||||||
|
nearestQValue(next.q),
|
||||||
|
next.filter_type,
|
||||||
|
next.bypass
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -340,6 +355,7 @@ function PeqBandList({
|
|||||||
|
|
||||||
const [qMin, qMax] = PEQ_Q_RANGES[peq.filter_type];
|
const [qMin, qMax] = PEQ_Q_RANGES[peq.filter_type];
|
||||||
const qValues = PEAK_Q_VALUES.filter((q) => q >= qMin && q <= qMax);
|
const qValues = PEAK_Q_VALUES.filter((q) => q >= qMin && q <= qMax);
|
||||||
|
const qValue = nearestQValue(peq.q, qValues.length ? qValues : PEAK_Q_VALUES);
|
||||||
|
|
||||||
const qDisabled = PEQ_Q_UNUSED.has(peq.filter_type);
|
const qDisabled = PEQ_Q_UNUSED.has(peq.filter_type);
|
||||||
const gainDisabled = !PEQ_GAIN_USED.has(peq.filter_type);
|
const gainDisabled = !PEQ_GAIN_USED.has(peq.filter_type);
|
||||||
@@ -376,7 +392,7 @@ function PeqBandList({
|
|||||||
|
|
||||||
<ChoiceStepper
|
<ChoiceStepper
|
||||||
className="peq-band-list__stepper"
|
className="peq-band-list__stepper"
|
||||||
value={peq.q}
|
value={qValue}
|
||||||
values={qValues}
|
values={qValues}
|
||||||
formatLabel={(v) => v.toFixed(2)}
|
formatLabel={(v) => v.toFixed(2)}
|
||||||
disabled={qDisabled}
|
disabled={qDisabled}
|
||||||
@@ -401,7 +417,14 @@ function PeqBandList({
|
|||||||
formatLabel={(v) =>
|
formatLabel={(v) =>
|
||||||
PEQ_FILTERS_LABEL.find((opt) => opt.value === v)?.label ?? String(v)
|
PEQ_FILTERS_LABEL.find((opt) => opt.value === v)?.label ?? String(v)
|
||||||
}
|
}
|
||||||
onChange={(value) => commit(band, { filter_type: value, q: 3.0 })}
|
onChange={(value) => {
|
||||||
|
const [min, max] = PEQ_Q_RANGES[value];
|
||||||
|
const allowed = PEAK_Q_VALUES.filter((q) => q >= min && q <= max);
|
||||||
|
commit(band, {
|
||||||
|
filter_type: value,
|
||||||
|
q: nearestQValue(3.0, allowed.length ? allowed : PEAK_Q_VALUES),
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -499,7 +522,7 @@ function ChannelSection({
|
|||||||
// Skip only if already flat — nothing to change.
|
// Skip only if already flat — nothing to change.
|
||||||
if (current.gain === 0 && current.filter_type === PEQFilter.Peak) continue;
|
if (current.gain === 0 && current.filter_type === PEQFilter.Peak) continue;
|
||||||
|
|
||||||
await commitPEQ(band, 0, current.frequency, current.q, PEQFilter.Peak, current.bypass);
|
await commitPEQ(band, 0, current.frequency, nearestQValue(3.0, PEAK_Q_VALUES), PEQFilter.Peak, current.bypass);
|
||||||
}
|
}
|
||||||
|
|
||||||
setResetting(false);
|
setResetting(false);
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ function CompressorSection({ dsp, set_compressor }: { dsp: DSP; set_compressor:
|
|||||||
const steps = 120;
|
const steps = 120;
|
||||||
const points: [number, number][] = [];
|
const points: [number, number][] = [];
|
||||||
for (let i = 0; i <= steps; i++) {
|
for (let i = 0; i <= steps; i++) {
|
||||||
const x = -100 + (100 * i) / steps;
|
const x = -100 + (120 * i) / steps;
|
||||||
points.push([x, compressorOutputDb(x, threshold, ratioNumeric, knee)]);
|
points.push([x, compressorOutputDb(x, threshold, ratioNumeric, knee)]);
|
||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
@@ -227,9 +227,9 @@ function CompressorSection({ dsp, set_compressor }: { dsp: DSP; set_compressor:
|
|||||||
|
|
||||||
<LinearGraph
|
<LinearGraph
|
||||||
xMin={-100}
|
xMin={-100}
|
||||||
xMax={0}
|
xMax={20}
|
||||||
yMin={-100}
|
yMin={-100}
|
||||||
yMax={0}
|
yMax={20}
|
||||||
xLabel="in dB"
|
xLabel="in dB"
|
||||||
yLabel="out dB"
|
yLabel="out dB"
|
||||||
formatX={(v) => Math.round(v).toString()}
|
formatX={(v) => Math.round(v).toString()}
|
||||||
@@ -240,7 +240,7 @@ function CompressorSection({ dsp, set_compressor }: { dsp: DSP; set_compressor:
|
|||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.setLineDash([4, 4]);
|
ctx.setLineDash([4, 4]);
|
||||||
const [ux0, uy0] = toPx(-100, -100);
|
const [ux0, uy0] = toPx(-100, -100);
|
||||||
const [ux1, uy1] = toPx(0, 0);
|
const [ux1, uy1] = toPx(20, 20);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(ux0, uy0);
|
ctx.moveTo(ux0, uy0);
|
||||||
ctx.lineTo(ux1, uy1);
|
ctx.lineTo(ux1, uy1);
|
||||||
@@ -252,7 +252,7 @@ function CompressorSection({ dsp, set_compressor }: { dsp: DSP; set_compressor:
|
|||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.setLineDash([2, 3]);
|
ctx.setLineDash([2, 3]);
|
||||||
const [tx0, ty0] = toPx(activeComp.threshold, -100);
|
const [tx0, ty0] = toPx(activeComp.threshold, -100);
|
||||||
const [tx1, ty1] = toPx(activeComp.threshold, 0);
|
const [tx1, ty1] = toPx(activeComp.threshold, 20);
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(tx0, ty0);
|
ctx.moveTo(tx0, ty0);
|
||||||
ctx.lineTo(tx1, ty1);
|
ctx.lineTo(tx1, ty1);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DSP } from '../../../../types';
|
import { DSP, isConnected } from '../../../../types';
|
||||||
import Button from '../../../../components/Button/Button';
|
import Button from '../../../../components/Button/Button';
|
||||||
import Card from '../../../../components/Card/Card';
|
import Card from '../../../../components/Card/Card';
|
||||||
import Led from '../../../../components/Led/Led';
|
import Led from '../../../../components/Led/Led';
|
||||||
@@ -14,13 +14,14 @@ function ConnectionPanel({
|
|||||||
onConnect: () => void;
|
onConnect: () => void;
|
||||||
onDisconnect: () => void;
|
onDisconnect: () => void;
|
||||||
}) {
|
}) {
|
||||||
const statusLabel =
|
|
||||||
dsp.status === 'connected'
|
|
||||||
? 'Connected'
|
|
||||||
: dsp.status === 'connecting'
|
|
||||||
? 'Connecting…'
|
|
||||||
: 'Disconnected';
|
|
||||||
|
|
||||||
|
const statusLabel =
|
||||||
|
isConnected(dsp)
|
||||||
|
? 'Connected'
|
||||||
|
: dsp.isConnecting
|
||||||
|
? 'Connecting…'
|
||||||
|
: 'Disconnected';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="connection-section">
|
<div className="connection-section">
|
||||||
<Card className="connection-panel">
|
<Card className="connection-panel">
|
||||||
@@ -35,10 +36,10 @@ function ConnectionPanel({
|
|||||||
|
|
||||||
<div className="connection-panel__status">
|
<div className="connection-panel__status">
|
||||||
<span className="connection-panel__led">
|
<span className="connection-panel__led">
|
||||||
<Led status={dsp.status} />
|
<Led status={dsp.isConnecting ? "connecting" : isConnected(dsp) ? "connected" : "disconnected"} />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span className={`status-label status-label--${dsp.status}`}>{statusLabel}</span>
|
<span>{statusLabel}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -64,13 +65,13 @@ function ConnectionPanel({
|
|||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<div className="connection-panel__actions">
|
<div className="connection-panel__actions">
|
||||||
{dsp.status === 'connected' ? (
|
{isConnected(dsp) ? (
|
||||||
<Button variant="outline" onClick={onDisconnect}>
|
<Button variant="outline" onClick={onDisconnect}>
|
||||||
Disconnect
|
Disconnect
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button variant="primary" onClick={onConnect} disabled={dsp.status === 'connecting'}>
|
<Button variant="primary" onClick={onConnect} disabled={dsp.isConnecting}>
|
||||||
{dsp.status === 'connecting' ? 'Connecting…' : 'Connect'}
|
{dsp.isConnecting ? 'Connecting…' : 'Connect'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useEffect, useState, type SetStateAction } from 'react';
|
||||||
import { Sidebar, SidebarItem } from '../../../../components/Sidebar/Sidebar';
|
import { Sidebar, SidebarItem } from '../../../../components/Sidebar/Sidebar';
|
||||||
import {
|
import {
|
||||||
BoltIcon,
|
BoltIcon,
|
||||||
@@ -30,10 +30,11 @@ import './DSP408Panel.css';
|
|||||||
import MiscellaneousPanel from '../MiscellaneousSection/MiscellaneousSection';
|
import MiscellaneousPanel from '../MiscellaneousSection/MiscellaneousSection';
|
||||||
import { PasswordDialog } from '../PasswordDialog/PasswordDialog';
|
import { PasswordDialog } from '../PasswordDialog/PasswordDialog';
|
||||||
|
|
||||||
|
const activeSidebarByDsp = new Map<number, SidebarId>();
|
||||||
|
|
||||||
function DSP408Panel({ dsp, api }: { dsp: DSP408; api: DSP408Api }) {
|
function DSP408Panel({ dsp, api }: { dsp: DSP408; api: DSP408Api }) {
|
||||||
const [activeSidebar, setActiveSidebar] = useState<SidebarId>('overview');
|
|
||||||
const [sidebarExpanded, setSidebarExpanded] = useState(false);
|
const [sidebarExpanded, setSidebarExpanded] = useState(false);
|
||||||
const [selectedPreset, setSelectedPreset] = useState(dsp.state?.presets.current_index ?? 0);
|
const [selectedPreset, setSelectedPreset] = useState(dsp.state.presets.current_index);
|
||||||
const [presetLoading, setPresetLoading] = useState(false);
|
const [presetLoading, setPresetLoading] = useState(false);
|
||||||
|
|
||||||
const inputStates = dsp.state.current_config.input_states;
|
const inputStates = dsp.state.current_config.input_states;
|
||||||
@@ -63,21 +64,36 @@ function DSP408Panel({ dsp, api }: { dsp: DSP408; api: DSP408Api }) {
|
|||||||
{ id: 'miscellaneous', label: 'Miscellaneous', icon: <BoltIcon /> },
|
{ id: 'miscellaneous', label: 'Miscellaneous', icon: <BoltIcon /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
const presets = dsp.state.presets.names
|
const presets = [
|
||||||
? [
|
{ id: 'f00', name: 'Factory Preset' },
|
||||||
{ id: 'f00', name: 'Factory Preset' },
|
...(dsp.state.presets.names.map((name, index) => ({
|
||||||
...dsp.state.presets.names.map((name, index) => ({
|
id: `u${String(index + 1).padStart(2, '0')}`,
|
||||||
id: `u${String(index + 1).padStart(2, '0')}`,
|
name,
|
||||||
name,
|
})) ??
|
||||||
})),
|
Array.from({ length: 20 }, (_, index) => ({
|
||||||
]
|
id: `u${String(index + 1).padStart(2, '0')}`,
|
||||||
: [
|
name: 'Default Preset',
|
||||||
{ id: 'f00', name: 'Factory Preset' },
|
}))),
|
||||||
...Array.from({ length: 20 }, (_, index) => ({
|
];
|
||||||
id: `u${String(index + 1).padStart(2, '0')}`,
|
|
||||||
name: 'Default Preset',
|
const [activeSidebar, setActiveSidebarState] = useState<SidebarId>(
|
||||||
})),
|
() => activeSidebarByDsp.get(dsp.id) ?? 'overview'
|
||||||
];
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const saved = activeSidebarByDsp.get(dsp.id) ?? 'overview';
|
||||||
|
setActiveSidebarState(saved);
|
||||||
|
}, [dsp.id]);
|
||||||
|
|
||||||
|
const setActiveSidebar = (value: SetStateAction<SidebarId>) => {
|
||||||
|
setActiveSidebarState(prev => {
|
||||||
|
const next = typeof value === 'function' ? value(prev) : value;
|
||||||
|
|
||||||
|
activeSidebarByDsp.set(dsp.id, next);
|
||||||
|
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onAppChange = async (id: string) => {
|
const onAppChange = async (id: string) => {
|
||||||
const presetIndex = presets.findIndex((preset) => preset.id === id);
|
const presetIndex = presets.findIndex((preset) => preset.id === id);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '../../constants';
|
} from '../../constants';
|
||||||
import { SidebarId } from '../../constants';
|
import { SidebarId } from '../../constants';
|
||||||
import SignalFlowMatrix from '../../../../components/SignalFlowMatrix/SignalFlowMatrix';
|
import SignalFlowMatrix from '../../../../components/SignalFlowMatrix/SignalFlowMatrix';
|
||||||
|
import { useState } from 'react';
|
||||||
import './Matrixsection.css';
|
import './Matrixsection.css';
|
||||||
|
|
||||||
const GAIN_VALUES: number[] = (() => {
|
const GAIN_VALUES: number[] = (() => {
|
||||||
@@ -39,6 +40,12 @@ function getRoutedInputs(dsp: DSP, output: OutputChannel): InputChannel[] {
|
|||||||
return getMatrixRoutes(dsp, output).connected;
|
return getMatrixRoutes(dsp, output).connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function snapToGain(g: number): number {
|
||||||
|
return GAIN_VALUES.reduce((best, v) =>
|
||||||
|
Math.abs(v - g) < Math.abs(best - g) ? v : best
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function MatrixSection({
|
function MatrixSection({
|
||||||
dsp,
|
dsp,
|
||||||
setMatrixRouting,
|
setMatrixRouting,
|
||||||
@@ -68,6 +75,21 @@ function MatrixSection({
|
|||||||
void setMatrixGain(input, output, gain);
|
void setMatrixGain(input, output, gain);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [liveGains, setLiveGains] = useState<Record<string, number>>({});
|
||||||
|
|
||||||
|
// value = number while wheeling, null once the commit is done
|
||||||
|
const previewGain = (input: InputChannel, output: OutputChannel, value: number | null) => {
|
||||||
|
const key = `${input}-${output}`;
|
||||||
|
setLiveGains((prev) => {
|
||||||
|
if (value === null) {
|
||||||
|
if (!(key in prev)) return prev;
|
||||||
|
const { [key]: _removed, ...rest } = prev;
|
||||||
|
return rest;
|
||||||
|
}
|
||||||
|
return { ...prev, [key]: value };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="matrix-flow">
|
<div className="matrix-flow">
|
||||||
@@ -76,6 +98,7 @@ function MatrixSection({
|
|||||||
setMatrixRouting={setMatrixRouting}
|
setMatrixRouting={setMatrixRouting}
|
||||||
setMatrixGain={setMatrixGain}
|
setMatrixGain={setMatrixGain}
|
||||||
setActiveSidebar={setActiveSidebar}
|
setActiveSidebar={setActiveSidebar}
|
||||||
|
onGainPreview={previewGain}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="matrix-section">
|
<div className="matrix-section">
|
||||||
@@ -90,7 +113,7 @@ function MatrixSection({
|
|||||||
<div className="matrix-panel__rows">
|
<div className="matrix-panel__rows">
|
||||||
{INPUT_ORDER.map((input) => {
|
{INPUT_ORDER.map((input) => {
|
||||||
const connected = isRouted(dsp, output, input);
|
const connected = isRouted(dsp, output, input);
|
||||||
const gain = getLinkGain(dsp, output, input);
|
const gain = liveGains[`${input}-${output}`] ?? getLinkGain(dsp, output, input);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="matrix-panel__row" key={String(input)}>
|
<div className="matrix-panel__row" key={String(input)}>
|
||||||
@@ -111,7 +134,7 @@ function MatrixSection({
|
|||||||
|
|
||||||
<ChoiceStepper
|
<ChoiceStepper
|
||||||
className="matrix-gain-stepper"
|
className="matrix-gain-stepper"
|
||||||
value={gain}
|
value={snapToGain(gain)}
|
||||||
values={GAIN_VALUES}
|
values={GAIN_VALUES}
|
||||||
formatLabel={(v: number) => `${v > 0 ? '+' : ''}${v.toFixed(1)} dB`}
|
formatLabel={(v: number) => `${v > 0 ? '+' : ''}${v.toFixed(1)} dB`}
|
||||||
onChange={(g) => handleGainChange(output, input, g)}
|
onChange={(g) => handleGainChange(output, input, g)}
|
||||||
|
|||||||
@@ -9,31 +9,13 @@
|
|||||||
.miscellaneous-panel {
|
.miscellaneous-panel {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 720px;
|
max-width: 720px;
|
||||||
/* margin: 0 auto; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.miscellaneous-panel__header {
|
|
||||||
margin-bottom: var(--space-6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.miscellaneous-panel__title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: var(--font-lg);
|
|
||||||
font-weight: var(--font-weight-semibold);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.miscellaneous-panel__hint {
|
|
||||||
margin: var(--space-2) 0 0;
|
|
||||||
font-size: var(--font-sm);
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sections */
|
/* Sections */
|
||||||
|
|
||||||
.miscellaneous-panel__section {
|
.miscellaneous-panel__section {
|
||||||
padding-top: var(--space-5);
|
padding-top: var(--space-2);
|
||||||
margin-top: var(--space-5);
|
margin-top: var(--space-2);
|
||||||
|
|
||||||
border-top: var(--border-width) solid var(--border-hairline);
|
border-top: var(--border-width) solid var(--border-hairline);
|
||||||
}
|
}
|
||||||
@@ -45,7 +27,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.miscellaneous-panel__section-header {
|
.miscellaneous-panel__section-header {
|
||||||
margin-bottom: var(--space-4);
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.miscellaneous-panel__section-header h2 {
|
.miscellaneous-panel__section-header h2 {
|
||||||
@@ -57,7 +41,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.miscellaneous-panel__section-header p {
|
.miscellaneous-panel__section-header p {
|
||||||
margin: var(--space-1) 0 0;
|
margin: 0;
|
||||||
|
|
||||||
font-size: var(--font-sm);
|
font-size: var(--font-sm);
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
@@ -74,10 +58,12 @@
|
|||||||
.miscellaneous-panel__field {
|
.miscellaneous-panel__field {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-2);
|
gap: 0;
|
||||||
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
|
font-family: var(--font-mono);
|
||||||
}
|
}
|
||||||
|
|
||||||
.miscellaneous-panel__field label {
|
.miscellaneous-panel__field label {
|
||||||
@@ -122,12 +108,12 @@
|
|||||||
|
|
||||||
/* Stepper */
|
/* Stepper */
|
||||||
|
|
||||||
.miscellaneous-panel__field .choice-stepper__input {
|
.miscellaneous-panel__choice-select {
|
||||||
border: none;
|
height: var(--card-control-height);
|
||||||
background: transparent;
|
min-height: var(--card-control-height);
|
||||||
font-family: var(--font-mono);
|
max-height: var(--card-control-height);
|
||||||
font-weight: var(--font-weight-normal);
|
box-sizing: border-box;
|
||||||
text-align: left;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
@@ -138,19 +124,62 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
|
|
||||||
margin-top: var(--space-4);
|
margin-top: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Danger */
|
/* Danger */
|
||||||
|
|
||||||
.miscellaneous-panel__section--danger {
|
.miscellaneous-panel__section--danger {
|
||||||
padding-top: var(--space-5);
|
padding-top: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.miscellaneous-panel__section--danger .miscellaneous-panel__section-header p {
|
.miscellaneous-panel__section--danger .miscellaneous-panel__section-header p {
|
||||||
max-width: 560px;
|
max-width: 560px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Password Input */
|
||||||
|
|
||||||
|
.miscellaneous-panel__password-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.miscellaneous-panel__password-wrap input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.miscellaneous-panel__password-toggle {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 3%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: auto;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--accent-brand);
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.miscellaneous-panel__password-toggle:hover {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.miscellaneous-panel__password-toggle svg {
|
||||||
|
width: auto;
|
||||||
|
height: 65%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Mobile */
|
/* Mobile */
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
@@ -218,43 +247,4 @@
|
|||||||
margin-top: var(--space-5);
|
margin-top: var(--space-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Password Input */
|
|
||||||
|
|
||||||
.miscellaneous-panel__password-wrap {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.miscellaneous-panel__password-wrap input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.miscellaneous-panel__password-toggle {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 3%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
width: auto;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--accent-brand);
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.miscellaneous-panel__password-toggle:hover {
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.miscellaneous-panel__password-toggle svg {
|
|
||||||
width: auto;
|
|
||||||
height: 65%;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -37,10 +37,8 @@ function MiscellaneousPanel({
|
|||||||
const [isLinkModalOpen, setIsLinkModalOpen] = useState(false);
|
const [isLinkModalOpen, setIsLinkModalOpen] = useState(false);
|
||||||
const [isCopyModalOpen, setIsCopyModalOpen] = useState(false);
|
const [isCopyModalOpen, setIsCopyModalOpen] = useState(false);
|
||||||
|
|
||||||
const [inputSource, setInputSourceState] = useState<InputSource>({
|
const [liveInputSource, setLiveInputSource] = useState<InputSource | null>(null);
|
||||||
type: InputType.Analog,
|
const inputSource = liveInputSource ?? dsp.state.current_config.input_source;
|
||||||
frequency: DiscreteFrequency.F20,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
||||||
@@ -51,18 +49,19 @@ function MiscellaneousPanel({
|
|||||||
frequency: type === InputType.SineWave ? inputSource.frequency : DiscreteFrequency.F20,
|
frequency: type === InputType.SineWave ? inputSource.frequency : DiscreteFrequency.F20,
|
||||||
};
|
};
|
||||||
|
|
||||||
setInputSourceState(nextSource);
|
setLiveInputSource(nextSource);
|
||||||
await setInputSource(nextSource);
|
const success = await setInputSource(nextSource);
|
||||||
|
|
||||||
|
if (success) setLiveInputSource(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFrequencyChange = async (frequency: DiscreteFrequency) => {
|
const handleFrequencyChange = async (frequency: DiscreteFrequency) => {
|
||||||
const nextSource: InputSource = {
|
const nextSource: InputSource = { ...inputSource, frequency };
|
||||||
...inputSource,
|
|
||||||
frequency,
|
|
||||||
};
|
|
||||||
|
|
||||||
setInputSourceState(nextSource);
|
setLiveInputSource(nextSource);
|
||||||
await setInputSource(nextSource);
|
const success = await setInputSource(nextSource);
|
||||||
|
|
||||||
|
if (success) setLiveInputSource(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFactoryReset = () => {
|
const handleFactoryReset = () => {
|
||||||
@@ -109,6 +108,7 @@ function MiscellaneousPanel({
|
|||||||
|
|
||||||
<ChoiceSelect<InputType>
|
<ChoiceSelect<InputType>
|
||||||
id="misc-input-type"
|
id="misc-input-type"
|
||||||
|
className='miscellaneous-panel__choice-select'
|
||||||
value={inputSource.type}
|
value={inputSource.type}
|
||||||
options={INPUT_TYPES}
|
options={INPUT_TYPES}
|
||||||
onChange={handleInputTypeChange}
|
onChange={handleInputTypeChange}
|
||||||
@@ -122,6 +122,7 @@ function MiscellaneousPanel({
|
|||||||
|
|
||||||
<ChoiceSelect<DiscreteFrequency>
|
<ChoiceSelect<DiscreteFrequency>
|
||||||
id="misc-frequency"
|
id="misc-frequency"
|
||||||
|
className='miscellaneous-panel__choice-select'
|
||||||
value={inputSource.frequency}
|
value={inputSource.frequency}
|
||||||
options={DISCRETE_FREQUENCIES}
|
options={DISCRETE_FREQUENCIES}
|
||||||
formatLabel={(frequency) => {
|
formatLabel={(frequency) => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
PEQBand,
|
PEQBand,
|
||||||
InputSource,
|
InputSource,
|
||||||
DSP408Snapshot,
|
DSP408Snapshot,
|
||||||
|
DSPMode,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { SetDsps, NotifyFn, updateDSP, setPollingPaused } from '../../dsp/dspShared';
|
import { SetDsps, NotifyFn, updateDSP, setPollingPaused } from '../../dsp/dspShared';
|
||||||
import { registerPollStopper } from '../../dsp/pollRegistry';
|
import { registerPollStopper } from '../../dsp/pollRegistry';
|
||||||
@@ -25,7 +26,7 @@ export type DSP408Api = ReturnType<typeof forDSP408>;
|
|||||||
const selectedIds = new Set<number>();
|
const selectedIds = new Set<number>();
|
||||||
const pollIntervals = new Map<number, ReturnType<typeof setInterval>>();
|
const pollIntervals = new Map<number, ReturnType<typeof setInterval>>();
|
||||||
|
|
||||||
const ACTIVE_POLL_INTERVAL_MS = 1000;
|
const ACTIVE_POLL_INTERVAL_MS = 100;
|
||||||
const BACKGROUND_POLL_INTERVAL_MS = 5000;
|
const BACKGROUND_POLL_INTERVAL_MS = 5000;
|
||||||
|
|
||||||
function pollIntervalFor(id: number): number {
|
function pollIntervalFor(id: number): number {
|
||||||
@@ -117,8 +118,8 @@ export async function create(params: {
|
|||||||
ip: params.ip,
|
ip: params.ip,
|
||||||
port: params.port,
|
port: params.port,
|
||||||
deviceId: params.deviceId,
|
deviceId: params.deviceId,
|
||||||
mode: snapshot.mode,
|
mode: params.demo ? DSPMode.Demo: DSPMode.Normal,
|
||||||
status: 'disconnected',
|
isConnecting: false,
|
||||||
pollingPaused: true,
|
pollingPaused: true,
|
||||||
state: snapshot.state,
|
state: snapshot.state,
|
||||||
input_meters: snapshot.input_meters,
|
input_meters: snapshot.input_meters,
|
||||||
@@ -130,6 +131,7 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) {
|
|||||||
async function updateDSPState(): Promise<DSP408['state']> {
|
async function updateDSPState(): Promise<DSP408['state']> {
|
||||||
try {
|
try {
|
||||||
const state = await invoke<DSP408['state']>('get_dsp_state', { id: dsp.id });
|
const state = await invoke<DSP408['state']>('get_dsp_state', { id: dsp.id });
|
||||||
|
|
||||||
updateDSP(setDsps, dsp.id, (d) => ({ ...d, state }) as DSP408);
|
updateDSP(setDsps, dsp.id, (d) => ({ ...d, state }) as DSP408);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
@@ -143,11 +145,13 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) {
|
|||||||
|
|
||||||
async function abortLocked() {
|
async function abortLocked() {
|
||||||
await invoke('disconnect_dsp408', { id: dsp.id }).catch(() => {});
|
await invoke('disconnect_dsp408', { id: dsp.id }).catch(() => {});
|
||||||
updateDSP(setDsps, dsp.id, (d) => ({ ...d, status: 'disconnected', passwordRequired: true }));
|
|
||||||
|
updateDSPState();
|
||||||
|
updateDSP(setDsps, dsp.id, (d) => ({ ...d, isConnecting: false, passwordRequired: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function connectInternal(password?: string) {
|
async function connectInternal(password?: string) {
|
||||||
updateDSP(setDsps, dsp.id, (d) => ({ ...d, status: 'connecting', passwordRequired: false }));
|
updateDSP(setDsps, dsp.id, (d) => ({ ...d, isConnecting: true, passwordRequired: false }));
|
||||||
try {
|
try {
|
||||||
await invoke('connect_dsp408', { id: dsp.id });
|
await invoke('connect_dsp408', { id: dsp.id });
|
||||||
|
|
||||||
@@ -159,13 +163,10 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
await invoke('unlock_device', { id: dsp.id, password });
|
||||||
await invoke('unlock_device', { id: dsp.id, password }); // adjust name/signature
|
|
||||||
} catch {
|
|
||||||
// wrong password
|
|
||||||
}
|
|
||||||
|
|
||||||
state = await updateDSPState();
|
state = await updateDSPState();
|
||||||
|
|
||||||
if (isLocked(state)) {
|
if (isLocked(state)) {
|
||||||
await abortLocked();
|
await abortLocked();
|
||||||
notify('Wrong password', 'error', dsp.name);
|
notify('Wrong password', 'error', dsp.name);
|
||||||
@@ -173,11 +174,11 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDSP(setDsps, dsp.id, (d) => ({ ...d, status: 'connected', pollingPaused: false }));
|
updateDSP(setDsps, dsp.id, (d) => ({ ...d, isConnecting: false, pollingPaused: false }));
|
||||||
notify('Connected', 'success', dsp.name);
|
notify('Connected', 'success', dsp.name);
|
||||||
startPolling(dsp, setDsps);
|
startPolling(dsp, setDsps);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
updateDSP(setDsps, dsp.id, (d) => ({ ...d, status: 'disconnected' }));
|
updateDSP(setDsps, dsp.id, (d) => ({ ...d, isConnecting: false }));
|
||||||
notify(`Connection failed: ${String(err)}`, 'error', dsp.name);
|
notify(`Connection failed: ${String(err)}`, 'error', dsp.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,9 +215,10 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) {
|
|||||||
await invoke('disconnect_dsp408', { id: dsp.id });
|
await invoke('disconnect_dsp408', { id: dsp.id });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await updateDSPState();
|
||||||
|
|
||||||
updateDSP(setDsps, dsp.id, (d) => ({
|
updateDSP(setDsps, dsp.id, (d) => ({
|
||||||
...d,
|
...d,
|
||||||
status: 'disconnected',
|
|
||||||
pollingPaused: true,
|
pollingPaused: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -243,15 +243,23 @@ export type DSPState = {
|
|||||||
flags: DeviceFlags;
|
flags: DeviceFlags;
|
||||||
presets: PresetBank;
|
presets: PresetBank;
|
||||||
current_config: DSPConfigState;
|
current_config: DSPConfigState;
|
||||||
|
connection: ConnectionStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DSPMode = 'demo' | 'disconnected' | 'connected';
|
export enum ConnectionStatus {
|
||||||
|
Connected = 'Connected',
|
||||||
|
Disconnected = 'Disconnected',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DSPMode {
|
||||||
|
Demo = 'Demo',
|
||||||
|
Normal = 'Normal',
|
||||||
|
}
|
||||||
|
|
||||||
export type DSP408Snapshot = {
|
export type DSP408Snapshot = {
|
||||||
state: DSPState;
|
state: DSPState;
|
||||||
input_meters: DSP408InputMeters;
|
input_meters: DSP408InputMeters;
|
||||||
output_meters: DSP408OutputMeters;
|
output_meters: DSP408OutputMeters;
|
||||||
mode: DSPMode;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Meter {
|
export interface Meter {
|
||||||
|
|||||||
@@ -139,16 +139,14 @@ function peqBandResponseDb(
|
|||||||
/** Sum of all bands' contributions at a given frequency. */
|
/** Sum of all bands' contributions at a given frequency. */
|
||||||
function peqTotalCurveDb(
|
function peqTotalCurveDb(
|
||||||
freq: number,
|
freq: number,
|
||||||
bands: Record<PEQBand, PEQ>,
|
bands: Partial<Record<PEQBand, PEQ>>,
|
||||||
sampleRate: number = DEFAULT_SAMPLE_RATE
|
sampleRate: number = DEFAULT_SAMPLE_RATE
|
||||||
): number {
|
): number {
|
||||||
let sum = 0;
|
return Object.values(bands).reduce(
|
||||||
|
(sum, band) =>
|
||||||
for (const band of Object.values(PEQBand)) {
|
band ? sum + peqBandResponseDb(freq, band, sampleRate) : sum,
|
||||||
sum += peqBandResponseDb(freq, bands[band], sampleRate);
|
0
|
||||||
}
|
);
|
||||||
|
|
||||||
return sum;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -307,16 +305,14 @@ function peqBandPhaseDeg(
|
|||||||
|
|
||||||
function peqTotalPhaseDeg(
|
function peqTotalPhaseDeg(
|
||||||
freq: number,
|
freq: number,
|
||||||
bands: Record<PEQBand, PEQ>,
|
bands: Partial<Record<PEQBand, PEQ>>,
|
||||||
sampleRate: number = DEFAULT_SAMPLE_RATE
|
sampleRate: number = DEFAULT_SAMPLE_RATE
|
||||||
): number {
|
): number {
|
||||||
let sum = 0;
|
return Object.values(bands).reduce(
|
||||||
|
(sum, band) =>
|
||||||
for (const band of Object.values(PEQBand)) {
|
band ? sum + peqBandPhaseDeg(freq, band, sampleRate) : sum,
|
||||||
sum += peqBandPhaseDeg(freq, bands[band], sampleRate);
|
0
|
||||||
}
|
);
|
||||||
|
|
||||||
return sum;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPeqPhasePoints({
|
export function getPeqPhasePoints({
|
||||||
|
|||||||
+7
-5
@@ -3,10 +3,9 @@ import {
|
|||||||
DSPMode,
|
DSPMode,
|
||||||
DSP408OutputMeters,
|
DSP408OutputMeters,
|
||||||
DSP408InputMeters,
|
DSP408InputMeters,
|
||||||
|
ConnectionStatus,
|
||||||
} from './features/dsp408/types';
|
} from './features/dsp408/types';
|
||||||
|
|
||||||
export type DSPStatus = 'connected' | 'connecting' | 'disconnected';
|
|
||||||
|
|
||||||
export type NotificationType = 'success' | 'error' | 'warning';
|
export type NotificationType = 'success' | 'error' | 'warning';
|
||||||
export type Notification = {
|
export type Notification = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -39,12 +38,15 @@ interface DSPBase {
|
|||||||
mode: DSPMode;
|
mode: DSPMode;
|
||||||
|
|
||||||
passwordRequired?: boolean;
|
passwordRequired?: boolean;
|
||||||
|
isConnecting: boolean;
|
||||||
status: DSPStatus;
|
|
||||||
|
|
||||||
pollingPaused: boolean;
|
pollingPaused: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isConnected(dsp: DSP): boolean {
|
||||||
|
return dsp.type === 'DSP408'
|
||||||
|
&& dsp.state.connection === ConnectionStatus.Connected;
|
||||||
|
}
|
||||||
|
|
||||||
export type DSP408 = DSPBase & {
|
export type DSP408 = DSPBase & {
|
||||||
type: 'DSP408';
|
type: 'DSP408';
|
||||||
state: DSP408State;
|
state: DSP408State;
|
||||||
|
|||||||
Reference in New Issue
Block a user