Compare commits

..
8 Commits
Author SHA1 Message Date
LucasDLTG e68e73d3d5 fix: render error on input 2026-09-30 21:14:08 +02:00
Lucas a5669805c8 fix: make led focus the tab 2026-09-30 16:55:47 +02:00
Lucas cef7336517 feat: remember panel per dsp 2026-09-30 16:53:44 +02:00
Lucas 50a3c1a1e5 fix: handle abort password 2026-09-30 16:35:15 +02:00
Lucas 684f5ce866 fix: too wide misc panel 2026-09-30 16:08:42 +02:00
Lucas 80da40f678 fix: disconnect button 2026-09-30 12:42:14 +02:00
LucasDLTG 3cca2f41f4 fix: connection led 2026-09-29 20:56:34 +02:00
Lucas 6511d9d007 unstable 2026-09-29 16:59:54 +02:00
13 changed files with 359 additions and 184 deletions
+4
View File
@@ -9,5 +9,9 @@ This template should help get you started developing with Tauri, React and Types
# 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 in meter if clip bool is limit instead
- On close disconenct all ds^p
- Ma-trix gain block sometimes
- Q out of range
- Check end to end behavioour - Check end to end behavioour
+185 -39
View File
@@ -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",
-2
View File
@@ -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
View File
@@ -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;
+15 -5
View File
@@ -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}>
@@ -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,12 +14,13 @@ function ConnectionPanel({
onConnect: () => void; onConnect: () => void;
onDisconnect: () => void; onDisconnect: () => void;
}) { }) {
const statusLabel =
dsp.status === 'connected' const statusLabel =
? 'Connected' isConnected(dsp)
: dsp.status === 'connecting' ? 'Connected'
? 'Connecting…' : dsp.isConnecting
: 'Disconnected'; ? 'Connecting…'
: 'Disconnected';
return ( return (
<div className="connection-section"> <div className="connection-section">
@@ -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);
@@ -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%;
}
@@ -109,6 +109,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 +123,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) => {
+15 -13
View File
@@ -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,
})); }));
+10 -2
View File
@@ -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 {
+12 -16
View File
@@ -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
View File
@@ -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;