From a98be2f8e3751008cc9eeb50409dff784ed60491 Mon Sep 17 00:00:00 2001 From: LucasDLTG Date: Sun, 13 Sep 2026 19:10:42 +0200 Subject: [PATCH] feat: add demo mode + rust own default state --- Cargo.lock | 647 +++++++++++- Cargo.toml | 3 +- src/dsp408/client/dsp408.rs | 1762 +++++++++++++++++++++------------ src/dsp408/default.rs | 272 +++++ src/dsp408/mapper/response.rs | 49 +- src/dsp408/mod.rs | 2 + src/dsp408/types.rs | 28 +- src/main.rs | 10 +- 8 files changed, 2112 insertions(+), 661 deletions(-) create mode 100644 src/dsp408/default.rs diff --git a/Cargo.lock b/Cargo.lock index 49cc4a9..bb75e73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,156 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cc" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags", + "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", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -15,29 +165,268 @@ dependencies = [ "dotenvy", "serde", "serde_json", + "serde_with", "strum", "strum_macros", "thiserror", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[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", +] + +[[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]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + [[package]] name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[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]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -56,6 +445,56 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "serde" version = "1.0.229" @@ -99,6 +538,57 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_with" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.2", + "jiff", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.28.0" @@ -141,30 +631,179 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", "syn 3.0.2", ] +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.2", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 6cffc80..12ccb37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,12 @@ edition = "2024" dotenvy = "0.15" [dependencies] -thiserror = "2.0.19" +thiserror = "2.0.20" strum = "0.28.0" strum_macros = "0.28.0" serde = { version = "1", features = ["derive"], optional = true } serde_json = { version = "1", optional = true } +serde_with = "3" [lib] name = "dsp_thomann" diff --git a/src/dsp408/client/dsp408.rs b/src/dsp408/client/dsp408.rs index 18246f6..e039e22 100644 --- a/src/dsp408/client/dsp408.rs +++ b/src/dsp408/client/dsp408.rs @@ -10,6 +10,9 @@ use std::{ use crate::{ common::errors::DSPError, dsp408::{ + default::default_input_meters, + default::default_output_meters, + default_dsp_state, mapper::{ self, constants::{CONFIG_CHUNK_INDEX_MAX, PRESET_INDEX_MAX, delay_range}, @@ -30,10 +33,12 @@ fn instances() -> &'static Mutex> { } pub struct DSP408 { - connection: types::DeviceConnection, // read-only so thread protected - socket: Arc>>, // thread protected - connected: bool, - state: Option, + mode: types::DSPMode, + connection: types::DeviceConnection, + socket: Arc>>, + state: types::DSPState, + input_meters: types::InputMeters, + output_meters: types::OutputMeters, } impl DSP408 { @@ -53,6 +58,8 @@ impl DSP408 { } Ok(Self { + mode: types::DSPMode::Disconnected, + connection: types::DeviceConnection { host, port, @@ -61,12 +68,32 @@ impl DSP408 { socket: Arc::new(Mutex::new(None)), - connected: false, + state: default_dsp_state(), - state: None, + input_meters: default_input_meters(), + output_meters: default_output_meters(), }) } + pub fn new_demo() -> Self { + Self { + mode: types::DSPMode::Demo, + + connection: types::DeviceConnection { + host: IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), + port: 0, + device_id: 1, + }, + + socket: Arc::new(Mutex::new(None)), + + state: default_dsp_state(), + + input_meters: default_input_meters(), + output_meters: default_output_meters(), + } + } + fn send_raw(socket: &mut TcpStream, data: &[u8]) -> Result<(), DSPError> { socket.write_all(data)?; @@ -109,20 +136,32 @@ impl DSP408 { Ok(response) } + pub fn mode(&self) -> types::DSPMode { + self.mode + } + pub fn connected(&self) -> bool { - self.connected + self.mode == types::DSPMode::Connected } - pub fn connection(&self) -> &types::DeviceConnection { - &self.connection + pub fn demo(&self) -> bool { + self.mode == types::DSPMode::Demo } - pub fn state(&self) -> Result<&types::DSPState, DSPError> { - self.state.as_ref().ok_or(DSPError::NotConnected) + pub fn state(&self) -> &types::DSPState { + &self.state } - fn state_mut(&mut self) -> Result<&mut types::DSPState, DSPError> { - self.state.as_mut().ok_or(DSPError::NotConnected) + pub fn input_meters(&self) -> &types::InputMeters { + &self.input_meters + } + + pub fn output_meters(&self) -> &types::OutputMeters { + &self.output_meters + } + + fn state_mut(&mut self) -> &mut types::DSPState { + &mut self.state } fn initialize_state(&mut self) -> Result<(), DSPError> { @@ -138,7 +177,7 @@ impl DSP408 { let state = self.read_config_state()?; - self.state = Some(types::DSPState { + self.state = types::DSPState { name, flags, presets: types::PresetBank { @@ -147,7 +186,7 @@ impl DSP408 { modified: modified.modified, }, current_config: state, - }); + }; Ok(()) } @@ -162,6 +201,14 @@ impl DSP408 { // ---------- Commands ---------- // Command `0x10` pub fn connect(&mut self) -> Result<(), DSPError> { + if self.mode == types::DSPMode::Demo { + return Ok(()); + } + + if self.mode == types::DSPMode::Connected { + return Err(DSPError::AlreadyConnected); + } + let key = (self.connection.host, self.connection.device_id); { @@ -220,20 +267,18 @@ impl DSP408 { let response = map_response(&frame)?; if matches!(response, types::DeviceResponseType::HandshakeAck(_)) { - let result = self.initialize_state(); - - if result.is_err() { - self.disconnect()?; - return result; + if let Err(error) = self.initialize_state() { + let _ = self.disconnect(); + return Err(error); } + self.mode = types::DSPMode::Connected; + instances() .lock() .map_err(|e| DSPError::InternalError(e.to_string()))? .insert(key, ()); - self.connected = true; - return Ok(()); } } @@ -247,21 +292,24 @@ impl DSP408 { pub fn disconnect(&mut self) -> Result<(), DSPError> { let key = (self.connection.host, self.connection.device_id); - let mut socket_guard = self.socket.lock()?; + let mut socket_guard = self + .socket + .lock() + .map_err(|e| DSPError::MutexPoisoned(e.to_string()))?; if let Some(mut socket) = socket_guard.take() { - socket - .write_all(&commands::build_disconnect()) - .map_err(DSPError::Io)?; - - socket - .shutdown(std::net::Shutdown::Both) - .map_err(DSPError::Io)?; + let _ = socket.write_all(&commands::build_disconnect()); + let _ = socket.shutdown(std::net::Shutdown::Both); } - instances().lock()?.remove(&key); + instances() + .lock() + .map_err(|e| DSPError::MutexPoisoned(e.to_string()))? + .remove(&key); - self.connected = false; + if self.mode == types::DSPMode::Connected { + self.mode = types::DSPMode::Disconnected; + } Ok(()) } @@ -315,66 +363,93 @@ impl DSP408 { pub fn set_delay_unit(&mut self, unit: types::DelayUnit) -> Result { let raw_unit = mapper::delay_unit_to_raw(unit)?; - let request = commands::build_set_delay_unit(raw_unit)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_set_delay_unit(raw_unit)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); - } - - let state = self.state_mut()?; - let old_unit = state.current_config.delay_unit; - - if old_unit != unit { - for input in state.current_config.input_states.values_mut() { - input.delay = Self::convert_delay_unit(input.delay, old_unit, unit); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } } - for output in state.current_config.output_states.values_mut() { - output.delay = Self::convert_delay_unit(output.delay, old_unit, unit); + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); } } - - state.current_config.delay_unit = unit; - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + // Apply the state change in both Connected and Demo. + let state = self.state_mut(); + let old_unit = state.current_config.delay_unit; + + if old_unit != unit { + for input in state.current_config.input_states.values_mut() { + input.delay = Self::convert_delay_unit(input.delay, old_unit, unit); + } + + for output in state.current_config.output_states.values_mut() { + output.delay = Self::convert_delay_unit(output.delay, old_unit, unit); + } + } + + state.current_config.delay_unit = unit; + + Ok(true) } // Command `0x20` (Recall) pub fn recall_preset(&mut self, preset_index: usize) -> Result { let raw_index = mapper::preset_index_to_raw(preset_index)?; - let request = commands::build_recall(raw_index)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_recall(raw_index)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + + let config = self.read_config_state()?; + self.state_mut().current_config = config; + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } - - let config = self.read_config_state()?; - - self.state_mut()?.current_config = config; - self.state_mut()?.presets.current_index = preset_index; - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => {} + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.state_mut().presets.current_index = preset_index; + + Ok(true) } // Command `0x21` (Store Preset) @@ -389,32 +464,42 @@ impl DSP408 { return Ok(false); } - let request = commands::build_store_current_configuration(raw_index)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_store_current_configuration(raw_index)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } if !self.request_acknowledgement()? { return Err(DSPError::OperationFailed( - "Store Configuration fails".into(), + "Store Configuration failed".into(), )); } - - let state = self.state_mut()?; - state.presets.modified[state.presets.current_index] = true; - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => {} + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.state_mut().presets.modified[preset_index] = true; + + Ok(true) } // Command `0x22` (Get Preset Modification Status) @@ -438,37 +523,69 @@ impl DSP408 { return Ok(false); } - let request = commands::build_factory_reset(); + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_factory_reset(); - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => Ok(ack.success), - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } + } + + // Hardware accepted the reset. + self.state = default_dsp_state(); + } + + types::DSPMode::Demo => { + self.state = default_dsp_state(); + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + Ok(true) } // Command `0x26` (Store Preset Name) pub fn set_current_preset_name(&mut self, name: &str) -> Result { let raw_name = mapper::preset_name_to_raw(name)?; - let request = commands::build_store_current_preset_name(&raw_name)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_store_current_preset_name(&raw_name)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } - let current_index = { - let state = self.state()?; - state.presets.current_index - }; + let current_index = self.state().presets.current_index; if !self.store_current_configuration(current_index, true)? { return Ok(false); @@ -477,17 +594,21 @@ impl DSP408 { if !self.request_acknowledgement()? { return Ok(false); } - - let state = self.state_mut()?; - state.presets.names[state.presets.current_index] = name.to_string(); - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware operations. + } + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + // Shared authoritative state update. + let current_index = self.state().presets.current_index; + + self.state_mut().presets.names[current_index - 1] = name.to_string(); + + Ok(true) } // Command `0x27 - 0x24` (Get Config Chunk) @@ -524,6 +645,55 @@ impl DSP408 { } } + fn copy_channel_state( + &mut self, + source: types::Channel, + destination: types::Channel, + ) -> Result<(), DSPError> { + match (source, destination) { + (types::Channel::Input(src), types::Channel::Input(dst)) => { + let src_state = self + .state + .current_config + .input_states + .get(&src) + .cloned() + .ok_or_else(|| DSPError::InternalError("missing input channel state".into()))?; + + self.state + .current_config + .input_states + .insert(dst, src_state); + } + + (types::Channel::Output(src), types::Channel::Output(dst)) => { + let src_state = self + .state + .current_config + .output_states + .get(&src) + .cloned() + .ok_or_else(|| { + DSPError::InternalError("missing output channel state".into()) + })?; + + self.state + .current_config + .output_states + .insert(dst, src_state); + } + + _ => { + return Err(DSPError::InternalError( + "copy_channel requires source and destination to be the same channel kind" + .into(), + )); + } + } + + Ok(()) + } + // Command `0x2A` (Copy Channel) pub fn copy_channel( &mut self, @@ -532,17 +702,40 @@ impl DSP408 { ) -> Result { let (raw_channel_1, raw_channel_2) = mapper::copy_channel_to_raw(channel_1, channel_2)?; - let request = commands::build_copy_channel(raw_channel_1, raw_channel_2)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_copy_channel(raw_channel_1, raw_channel_2)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => Ok(ack.success), - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } + } + } + + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.copy_channel_state(channel_1, channel_2)?; + + Ok(true) } // Command `0x2C` (Device Flags) @@ -564,52 +757,80 @@ impl DSP408 { pub fn unlock_device(&mut self, password: &str) -> Result { let raw_password = mapper::password_to_raw(password)?; - let request = commands::build_unlock_device(&raw_password)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_unlock_device(&raw_password)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::AuthenticationResult(result) => { - if !result.success { - return Ok(false); + match resp { + types::DeviceResponseType::AuthenticationResult(result) => { + if !result.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected AuthenticationResult, got {:?}", + other + ))); + } } - - let state = self.state_mut()?; - state.flags.is_locked = false; - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected AuthenticationResult, got {:?}", - other - ))), + + types::DSPMode::Demo => { + // Emulate successful unlock locally. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.state_mut().flags.is_locked = false; + + Ok(true) } // Command `0x2F` (Lock Device) pub fn lock_device(&mut self, password: &str) -> Result { let raw_password = mapper::password_to_raw(password)?; - let request = commands::build_lock_device(&raw_password)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_lock_device(&raw_password)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } - - let state = self.state_mut()?; - state.flags.is_locked = true; - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + + types::DSPMode::Demo => { + // Emulate locking locally. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.state_mut().flags.is_locked = true; + + Ok(true) } // Command `0x30` (Set Compressor) @@ -629,40 +850,57 @@ impl DSP408 { let raw_knee = mapper::knee_db_to_raw(knee)?; let raw_threshold = mapper::threshold_db_to_raw(threshold)?; - let request = commands::build_set_compressor( - raw_channel, - raw_ratio, - raw_attack, - raw_release, - raw_knee, - raw_threshold, - )?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_set_compressor( + raw_channel, + raw_ratio, + raw_attack, + raw_release, + raw_knee, + raw_threshold, + )?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } - - let state = self.state_mut()?; - - state.current_config.output_state_mut(channel).compressor = types::Compressor { - threshold, - ratio, - attack, - release, - knee, - }; - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + // Shared authoritative state update. + self.state_mut() + .current_config + .output_state_mut(channel) + .compressor = types::Compressor { + threshold, + ratio, + attack, + release, + knee, + }; + + Ok(true) } // Command `0x31` (Set Low-Pass Filter) @@ -676,50 +914,64 @@ impl DSP408 { let raw_freq = mapper::frequency_hz_to_raw(freq)?; let raw_filter = mapper::crossover_filter_to_raw(filter_type)?; - let request = commands::build_low_pass(raw_channel, raw_freq, raw_filter)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_low_pass(raw_channel, raw_freq, raw_filter)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); - } - - let state = self.state_mut()?; - - match channel { - types::Channel::Input(input) => { - state - .current_config - .input_state_mut(input) - .crossover - .low_pass = types::Crossover { - frequency: freq, - slope: filter_type, - }; + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } } - types::Channel::Output(output) => { - state - .current_config - .output_state_mut(output) - .crossover - .low_pass = types::Crossover { - frequency: freq, - slope: filter_type, - }; + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); } } - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + let state = self.state_mut(); + + match channel { + types::Channel::Input(input) => { + state + .current_config + .input_state_mut(input) + .crossover + .low_pass = types::Crossover { + frequency: freq, + slope: filter_type, + }; + } + + types::Channel::Output(output) => { + state + .current_config + .output_state_mut(output) + .crossover + .low_pass = types::Crossover { + frequency: freq, + slope: filter_type, + }; + } + } + + Ok(true) } // Command `0x32` (Set High-Pass Filter) @@ -733,50 +985,64 @@ impl DSP408 { let raw_freq = mapper::frequency_hz_to_raw(freq)?; let raw_filter = mapper::crossover_filter_to_raw(filter_type)?; - let request = commands::build_high_pass(raw_channel, raw_freq, raw_filter)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_high_pass(raw_channel, raw_freq, raw_filter)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); - } - - let state = self.state_mut()?; - - match channel { - types::Channel::Input(input) => { - state - .current_config - .input_state_mut(input) - .crossover - .high_pass = types::Crossover { - frequency: freq, - slope: filter_type, - }; + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } } - types::Channel::Output(output) => { - state - .current_config - .output_state_mut(output) - .crossover - .high_pass = types::Crossover { - frequency: freq, - slope: filter_type, - }; + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); } } - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + let state = self.state_mut(); + + match channel { + types::Channel::Input(input) => { + state + .current_config + .input_state_mut(input) + .crossover + .high_pass = types::Crossover { + frequency: freq, + slope: filter_type, + }; + } + + types::Channel::Output(output) => { + state + .current_config + .output_state_mut(output) + .crossover + .high_pass = types::Crossover { + frequency: freq, + slope: filter_type, + }; + } + } + + Ok(true) } // Command `0x33` (Set PEQ Band) @@ -798,62 +1064,77 @@ impl DSP408 { let raw_q = mapper::peq_q_to_raw(q, filter_type)?; let raw_filter = mapper::peq_filter_to_raw(filter_type)?; - let request = commands::build_peq_band( - raw_channel, - raw_band, - raw_gain, - raw_frequency, - raw_q, - raw_filter, - bypass as u8, - )?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_peq_band( + raw_channel, + raw_band, + raw_gain, + raw_frequency, + raw_q, + raw_filter, + bypass as u8, + )?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); - } - - let state = self.state_mut()?; - - let peq = types::PEQ { - gain: gain_db, - frequency: freq_hz, - q, - filter_type, - bypass, - }; - - match channel { - types::Channel::Input(input) => { - state - .current_config - .input_state_mut(input) - .peq_chain - .bands - .insert(band, peq); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } } - types::Channel::Output(output) => { - state - .current_config - .output_state_mut(output) - .peq_chain - .bands - .insert(band, peq); + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); } } - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + // Shared authoritative state update (Connected + Demo). + let state = self.state_mut(); + + let peq = types::PEQ { + gain: gain_db, + frequency: freq_hz, + q, + filter_type, + bypass, + }; + + match channel { + types::Channel::Input(input) => { + state + .current_config + .input_state_mut(input) + .peq_chain + .bands + .insert(band, peq); + } + + types::Channel::Output(output) => { + state + .current_config + .output_state_mut(output) + .peq_chain + .bands + .insert(band, peq); + } + } + + Ok(true) } // Command `0x34` (Set Gain) @@ -861,72 +1142,100 @@ impl DSP408 { let raw_channel = mapper::channel_to_raw(channel)?; let raw_db = mapper::gain_db_to_raw(db)?; - let request = commands::build_gain(raw_channel, raw_db)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_gain(raw_channel, raw_db)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); - } - - let state = self.state_mut()?; - - match channel { - types::Channel::Input(input) => { - state.current_config.input_state_mut(input).gain = db; + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } } - types::Channel::Output(output) => { - state.current_config.output_state_mut(output).gain = db; + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); } } - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + let state = self.state_mut(); + + match channel { + types::Channel::Input(input) => { + state.current_config.input_state_mut(input).gain = db; + } + + types::Channel::Output(output) => { + state.current_config.output_state_mut(output).gain = db; + } + } + + Ok(true) } // Command `0x35` (Set Mute) pub fn set_mute(&mut self, channel: types::Channel, muted: bool) -> Result { let raw_channel = mapper::channel_to_raw(channel)?; - let request = commands::build_mute(raw_channel, muted as u8)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_mute(raw_channel, muted as u8)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); - } - - let state = self.state_mut()?; - - match channel { - types::Channel::Input(input) => { - state.current_config.input_state_mut(input).mute = muted; + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } } - types::Channel::Output(output) => { - state.current_config.output_state_mut(output).mute = muted; + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); } } - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + let state = self.state_mut(); + + match channel { + types::Channel::Input(input) => { + state.current_config.input_state_mut(input).mute = muted; + } + + types::Channel::Output(output) => { + state.current_config.output_state_mut(output).mute = muted; + } + } + + Ok(true) } // Command `0x36` (Invert Gain) @@ -937,36 +1246,50 @@ impl DSP408 { ) -> Result { let raw_channel = mapper::channel_to_raw(channel)?; - let request = commands::build_inverted_gain(raw_channel, inverted as u8)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_inverted_gain(raw_channel, inverted as u8)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); - } - - let state = self.state_mut()?; - - match channel { - types::Channel::Input(input) => { - state.current_config.input_state_mut(input).phase_inverted = inverted; + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } } - types::Channel::Output(output) => { - state.current_config.output_state_mut(output).phase_inverted = inverted; + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); } } - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + let state = self.state_mut(); + + match channel { + types::Channel::Input(input) => { + state.current_config.input_state_mut(input).phase_inverted = inverted; + } + + types::Channel::Output(output) => { + state.current_config.output_state_mut(output).phase_inverted = inverted; + } + } + + Ok(true) } // Command `0x38` (Set Delay) @@ -979,38 +1302,51 @@ impl DSP408 { let raw_channel = mapper::channel_to_raw(channel)?; let raw_delay = mapper::delay_to_raw(delay, unit)?; - let request = commands::build_set_delay(raw_channel, raw_delay)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_set_delay(raw_channel, raw_delay)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); - } - - let state = self.state_mut()?; - - let delay = mapper::raw_to_delay(raw_delay, unit)?; - - match channel { - types::Channel::Input(input) => { - state.current_config.input_state_mut(input).delay = delay; + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } } - types::Channel::Output(output) => { - state.current_config.output_state_mut(output).delay = delay; + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); } } - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + let state = self.state_mut(); + let delay = mapper::raw_to_delay(raw_delay, unit)?; + + match channel { + types::Channel::Input(input) => { + state.current_config.input_state_mut(input).delay = delay; + } + + types::Channel::Output(output) => { + state.current_config.output_state_mut(output).delay = delay; + } + } + + Ok(true) } // Command `0x39` (Set Input Source) @@ -1018,27 +1354,40 @@ impl DSP408 { let raw_source = mapper::input_source_to_raw(input_source.r#type)?; let raw_frequency = mapper::frequency_value_to_raw(input_source.frequency)?; - let request = commands::build_set_input_source(raw_source, raw_frequency)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_set_input_source(raw_source, raw_frequency)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } - - let state = self.state_mut()?; - state.current_config.input_source = input_source; - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.state_mut().current_config.input_source = input_source; + + Ok(true) } // Command `0x3A` (Matrix Routing) @@ -1050,32 +1399,44 @@ impl DSP408 { let raw_output = mapper::output_channel_to_raw(output)?; let raw_inputs = mapper::matrix_to_raw(&inputs)?; - let request = commands::build_matrix_routing(raw_output, raw_inputs)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_matrix_routing(raw_output, raw_inputs)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } - - let state = self.state_mut()?; - - state - .current_config - .output_state_mut(output) - .matrix_routes - .connected = inputs; - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.state_mut() + .current_config + .output_state_mut(output) + .matrix_routes + .connected = inputs; + + Ok(true) } // Command `0x3B` (Link Channel) @@ -1087,7 +1448,9 @@ impl DSP408 { let raw_source = mapper::channel_to_raw(source)?; let bitmask = mapper::link_channels_to_raw(source, &destinations)?; - // Copy source configuration before linking + // Copy source configuration to each destination first. This runs in + // both Connected and Demo mode (copy_channel handles that dispatch + // itself), and returns NotConnected for us if we're Disconnected. for destination in &destinations { if *destination == source { continue; @@ -1098,62 +1461,73 @@ impl DSP408 { } } - // Link source - let request = commands::build_link_channel(raw_source, bitmask)?; + match self.mode { + types::DSPMode::Connected => { + // Link source + let request = commands::build_link_channel(raw_source, bitmask)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); - } - } + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } - other => { - return Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))); - } - } - - // Clear destination links - for destination in &destinations { - if *destination == source { - continue; - } - - let raw_destination = mapper::channel_to_raw(*destination)?; - - let request = commands::build_link_channel(raw_destination, 0x00)?; - - let resp = self.send_recv(&request)?; - - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); } } - other => { - return Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))); + // Clear destination links + for destination in &destinations { + if *destination == source { + continue; + } + + let raw_destination = mapper::channel_to_raw(*destination)?; + + let request = commands::build_link_channel(raw_destination, 0x00)?; + + let resp = self.send_recv(&request)?; + + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } + } } + + if !self.request_acknowledgement()? { + return Ok(false); + } + + let config = self.read_config_state()?; + self.state_mut().current_config = config; + } + + types::DSPMode::Demo => { + // Copy already applied above; nothing else to reconcile. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); } } - if !self.request_acknowledgement()? { - return Ok(false); - } - - let config = self.read_config_state()?; - - self.state_mut()?.current_config = config; - Ok(true) } @@ -1165,40 +1539,54 @@ impl DSP408 { ) -> Result { let raw_channel = mapper::channel_to_raw(channel)?; - let request = commands::build_bypass_peq(raw_channel, bypass as u8)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_bypass_peq(raw_channel, bypass as u8)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); - } - - let state = self.state_mut()?; - - match channel { - types::Channel::Input(input) => { - state.current_config.input_state_mut(input).peq_chain.bypass = bypass; + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } } - types::Channel::Output(output) => { - state - .current_config - .output_state_mut(output) - .peq_chain - .bypass = bypass; + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); } } - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + let state = self.state_mut(); + + match channel { + types::Channel::Input(input) => { + state.current_config.input_state_mut(input).peq_chain.bypass = bypass; + } + + types::Channel::Output(output) => { + state + .current_config + .output_state_mut(output) + .peq_chain + .bypass = bypass; + } + } + + Ok(true) } // Command `0x3D` (Set Channel Name) @@ -1210,36 +1598,50 @@ impl DSP408 { let raw_channel = mapper::channel_to_raw(channel)?; let raw_name = mapper::channel_name_to_raw(name)?; - let request = commands::build_store_channel_name(raw_channel, &raw_name)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_store_channel_name(raw_channel, &raw_name)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); - } - - let state = self.state_mut()?; - - match channel { - types::Channel::Input(input) => { - state.current_config.input_state_mut(input).name = name.to_string(); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } } - types::Channel::Output(output) => { - state.current_config.output_state_mut(output).name = name.to_string(); + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); } } - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + let state = self.state_mut(); + + match channel { + types::Channel::Input(input) => { + state.current_config.input_state_mut(input).name = name.to_string(); + } + + types::Channel::Output(output) => { + state.current_config.output_state_mut(output).name = name.to_string(); + } + } + + Ok(true) } // Command `0x3E` (Set Gate Parameters) @@ -1257,39 +1659,54 @@ impl DSP408 { let raw_hold = mapper::hold_ms_to_raw(hold)?; let raw_release = mapper::release_ms_to_raw(release)?; - let request = commands::build_set_gate( - raw_channel, - raw_threshold, - raw_attack, - raw_hold, - raw_release, - )?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_set_gate( + raw_channel, + raw_threshold, + raw_attack, + raw_hold, + raw_release, + )?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } - - let state = self.state_mut()?; - - state.current_config.input_state_mut(channel).gate = types::Gate { - attack, - release, - hold, - threshold, - }; - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.state_mut() + .current_config + .input_state_mut(channel) + .gate = types::Gate { + attack, + release, + hold, + threshold, + }; + + Ok(true) } // Command `0x3F` (Set Output Limiter) @@ -1305,48 +1722,84 @@ impl DSP408 { let raw_attack = mapper::attack_ms_to_raw(attack)?; let raw_release = mapper::release_ms_to_raw(release)?; - let request = - commands::build_set_limiter(raw_channel, raw_threshold, raw_attack, raw_release)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_set_limiter( + raw_channel, + raw_threshold, + raw_attack, + raw_release, + )?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } - - let state = self.state_mut()?; - - state.current_config.output_state_mut(channel).limiter = types::Limiter { - threshold, - attack, - release, - }; - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.state_mut() + .current_config + .output_state_mut(channel) + .limiter = types::Limiter { + threshold, + attack, + release, + }; + + Ok(true) } - // Command `0x40` (Get Level Meters) - pub fn get_meter_levels(&mut self) -> Result { - let request = commands::build_get_meter_levels(); + // Command `0x40` (Get Meter Level) + pub fn get_meter_levels( + &mut self, + ) -> Result<(types::InputMeters, types::OutputMeters), DSPError> { + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_get_meter_levels(); - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Meters(meters) => Ok(meters), + match resp { + types::DeviceResponseType::Meters(inputs, outputs) => { + self.input_meters = inputs.clone(); + self.output_meters = outputs.clone(); + Ok((inputs, outputs)) + } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Meters, got {:?}", - other - ))), + other => Err(DSPError::UnexpectedResponse(format!( + "Expected Meters, got {:?}", + other + ))), + } + } + + types::DSPMode::Demo => { + // Return the locally simulated meter values. + Ok((self.input_meters.clone(), self.output_meters.clone())) + } + + types::DSPMode::Disconnected => Err(DSPError::NotConnected), } } @@ -1361,33 +1814,46 @@ impl DSP408 { let raw_output_channel = mapper::output_channel_to_raw(output_channel)?; let raw_gain = mapper::matrix_gain_db_to_raw(gain)?; - let request = commands::build_matrix_gain(raw_input_channel, raw_output_channel, raw_gain)?; + match self.mode { + types::DSPMode::Connected => { + let request = + commands::build_matrix_gain(raw_input_channel, raw_output_channel, raw_gain)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } - - let state = self.state_mut()?; - - state - .current_config - .output_state_mut(output_channel) - .matrix_routes - .gains - .insert(input_channel, gain); - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.state_mut() + .current_config + .output_state_mut(output_channel) + .matrix_routes + .gains + .insert(input_channel, gain); + + Ok(true) } // Command `0x48` (Set GEQ Band) @@ -1401,34 +1867,46 @@ impl DSP408 { let raw_frequency_band = mapper::frequency_value_to_raw(frequency_band)?; let raw_gain = mapper::eq_gain_db_to_raw(gain)?; - let request = - commands::build_set_geq_band(raw_input_channel, raw_frequency_band, raw_gain)?; + match self.mode { + types::DSPMode::Connected => { + let request = + commands::build_set_geq_band(raw_input_channel, raw_frequency_band, raw_gain)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } - - let state = self.state_mut()?; - - state - .current_config - .input_state_mut(input_channel) - .geq - .gains - .insert(frequency_band, gain); - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.state_mut() + .current_config + .input_state_mut(input_channel) + .geq + .gains + .insert(frequency_band, gain); + + Ok(true) } // Command `0x49` (Set GEQ Bypass) @@ -1439,32 +1917,44 @@ impl DSP408 { ) -> Result { let raw_input_channel = mapper::input_channel_to_raw(input_channel)?; - let request = commands::build_set_geq_bypass(raw_input_channel, bypass as u8)?; + match self.mode { + types::DSPMode::Connected => { + let request = commands::build_set_geq_bypass(raw_input_channel, bypass as u8)?; - let resp = self.send_recv(&request)?; + let resp = self.send_recv(&request)?; - match resp { - types::DeviceResponseType::Acknowledgement(ack) => { - if !ack.success { - return Ok(false); + match resp { + types::DeviceResponseType::Acknowledgement(ack) => { + if !ack.success { + return Ok(false); + } + } + + other => { + return Err(DSPError::UnexpectedResponse(format!( + "Expected Acknowledgement, got {:?}", + other + ))); + } } - - let state = self.state_mut()?; - - state - .current_config - .input_state_mut(input_channel) - .geq - .bypass = bypass; - - Ok(true) } - other => Err(DSPError::UnexpectedResponse(format!( - "Expected Acknowledgement, got {:?}", - other - ))), + types::DSPMode::Demo => { + // No hardware request. + } + + types::DSPMode::Disconnected => { + return Err(DSPError::NotConnected); + } } + + self.state_mut() + .current_config + .input_state_mut(input_channel) + .geq + .bypass = bypass; + + Ok(true) } fn read_config_state(&mut self) -> Result { @@ -1487,7 +1977,7 @@ impl DSP408 { impl Drop for DSP408 { fn drop(&mut self) { - if self.connected { + if self.connected() { let _ = self.disconnect(); } } diff --git a/src/dsp408/default.rs b/src/dsp408/default.rs new file mode 100644 index 0000000..a586971 --- /dev/null +++ b/src/dsp408/default.rs @@ -0,0 +1,272 @@ +use std::collections::HashMap; +use strum::IntoEnumIterator; + +use super::types::{ + Compressor, Crossover, CrossoverFilter, CrossoverFilters, DSPConfigState, DSPState, DelayUnit, + DeviceFlags, DiscreteFrequency, Gate, GraphicEQ, InputChannel, InputChannelState, InputMeters, + InputSource, InputType, Limiter, MatrixRoutes, Meter, OutputChannel, OutputChannelState, + OutputMeters, PEQ, PEQBand, PEQChain, PEQFilter, PresetBank, Ratio, +}; + +const FREQUENCY_MIN: f32 = 19.7; +const FREQUENCY_MAX: f32 = 20160.0; + +const PRESET_COUNT: usize = 20; + +pub fn default_dsp_state() -> DSPState { + DSPState { + name: "Default".to_string(), + + flags: DeviceFlags { is_locked: false }, + + presets: default_preset_bank(), + + current_config: default_config(), + } +} + +pub fn default_input_meters() -> InputMeters { + InputMeters { + meters: InputChannel::iter().map(|c| (c, default_meter())).collect(), + } +} + +pub fn default_output_meters() -> OutputMeters { + OutputMeters { + meters: OutputChannel::iter() + .map(|c| (c, default_meter())) + .collect(), + } +} + +// ----------------------------------------------------------------------------- +// Presets +// ----------------------------------------------------------------------------- + +fn default_preset_bank() -> PresetBank { + PresetBank { + current_index: 0, + names: vec!["Default".to_string(); PRESET_COUNT], + modified: vec![false; PRESET_COUNT], + } +} + +// ----------------------------------------------------------------------------- +// DSP config +// ----------------------------------------------------------------------------- + +fn default_config() -> DSPConfigState { + DSPConfigState { + input_source: InputSource { + r#type: InputType::Analog, + frequency: DiscreteFrequency::F20000, + }, + + delay_unit: DelayUnit::Millisecond, + + input_states: default_input_states(), + output_states: default_output_states(), + } +} + +// ----------------------------------------------------------------------------- +// Input channels +// ----------------------------------------------------------------------------- + +fn default_input_states() -> HashMap { + [ + (InputChannel::InA, default_input_channel_state("In A")), + (InputChannel::InB, default_input_channel_state("In B")), + (InputChannel::InC, default_input_channel_state("In C")), + (InputChannel::InD, default_input_channel_state("In D")), + ] + .into_iter() + .collect() +} + +fn default_input_channel_state(name: &str) -> InputChannelState { + InputChannelState { + name: name.to_string(), + + gain: 0.0, + phase_inverted: false, + delay: 0.0, + + gate: default_gate(), + geq: default_graphic_eq(), + peq_chain: default_peq_chain(), + crossover: default_crossover_filters(), + + linked_channels: Vec::new(), + + mute: false, + } +} + +// ----------------------------------------------------------------------------- +// Output channels +// ----------------------------------------------------------------------------- + +fn default_output_states() -> HashMap { + [ + (OutputChannel::Out1, default_output_channel_state("Out 1")), + (OutputChannel::Out2, default_output_channel_state("Out 2")), + (OutputChannel::Out3, default_output_channel_state("Out 3")), + (OutputChannel::Out4, default_output_channel_state("Out 4")), + (OutputChannel::Out5, default_output_channel_state("Out 5")), + (OutputChannel::Out6, default_output_channel_state("Out 6")), + (OutputChannel::Out7, default_output_channel_state("Out 7")), + (OutputChannel::Out8, default_output_channel_state("Out 8")), + ] + .into_iter() + .collect() +} + +fn default_output_channel_state(name: &str) -> OutputChannelState { + OutputChannelState { + name: name.to_string(), + + matrix_routes: default_matrix_routes(), + + crossover: default_crossover_filters(), + peq_chain: default_peq_chain(), + + compressor: default_compressor(), + limiter: default_limiter(), + + gain: 0.0, + phase_inverted: false, + delay: 0.0, + + linked_channels: Vec::new(), + + mute: false, + } +} + +// ----------------------------------------------------------------------------- +// Gate +// ----------------------------------------------------------------------------- + +fn default_gate() -> Gate { + Gate { + attack: 10, + release: 10, + hold: 10, + threshold: 0.0, + } +} + +// ----------------------------------------------------------------------------- +// Graphic EQ +// ----------------------------------------------------------------------------- + +fn default_graphic_eq() -> GraphicEQ { + let gains = DiscreteFrequency::iter() + .map(|frequency| (frequency, 0.0)) + .collect(); + + GraphicEQ { + gains, + bypass: false, + } +} + +// ----------------------------------------------------------------------------- +// PEQ +// ----------------------------------------------------------------------------- + +fn default_peq() -> PEQ { + PEQ { + gain: 0.0, + frequency: 1000.0, + q: 1.0, + filter_type: PEQFilter::Peak, + bypass: false, + } +} + +fn default_peq_chain() -> PEQChain { + let bands = PEQBand::iter().map(|band| (band, default_peq())).collect(); + + PEQChain { + bands, + bypass: false, + } +} + +// ----------------------------------------------------------------------------- +// Crossover +// ----------------------------------------------------------------------------- + +fn default_crossover(frequency: f32) -> Crossover { + Crossover { + frequency, + slope: CrossoverFilter::Bypass, + } +} + +fn default_crossover_filters() -> CrossoverFilters { + CrossoverFilters { + high_pass: default_crossover(FREQUENCY_MIN), + low_pass: default_crossover(FREQUENCY_MAX), + } +} + +// ----------------------------------------------------------------------------- +// Matrix +// ----------------------------------------------------------------------------- + +fn default_matrix_routes() -> MatrixRoutes { + let gains = [ + (InputChannel::InA, 0.0), + (InputChannel::InB, 0.0), + (InputChannel::InC, 0.0), + (InputChannel::InD, 0.0), + ] + .into_iter() + .collect(); + + MatrixRoutes { + connected: vec![InputChannel::InA], + gains, + } +} + +// ----------------------------------------------------------------------------- +// Compressor +// ----------------------------------------------------------------------------- + +fn default_compressor() -> Compressor { + Compressor { + threshold: -90.0, + ratio: Ratio::Ratio1_1, + attack: 1, + release: 10, + knee: 0, + } +} + +// ----------------------------------------------------------------------------- +// Limiter +// ----------------------------------------------------------------------------- + +fn default_limiter() -> Limiter { + Limiter { + threshold: -90.0, + attack: 1, + release: 10, + } +} + +// ----------------------------------------------------------------------------- +// Meters +// ----------------------------------------------------------------------------- + +fn default_meter() -> Meter { + Meter { + rms: -60.0, + peak: -60.0, + clip: false, + } +} diff --git a/src/dsp408/mapper/response.rs b/src/dsp408/mapper/response.rs index d7015cb..39836f7 100644 --- a/src/dsp408/mapper/response.rs +++ b/src/dsp408/mapper/response.rs @@ -1,11 +1,16 @@ +use std::collections::HashMap; + +use strum::IntoEnumIterator; + use crate::{ common::errors::ProtocolError, dsp408::{ protocol::frame::Frame, types::{ Acknowledgement, AuthenticationResult, ConfigChunk, CurrentPreset, DeviceFlags, - DeviceInfo, DeviceResponseType, HandshakeAck, Meter, Meters, ModifiedPreset, - NotImplemented, PresetName, UnknownResponse, + DeviceInfo, DeviceResponseType, HandshakeAck, InputChannel, InputMeters, Meter, + ModifiedPreset, NotImplemented, OutputChannel, OutputMeters, PresetName, + UnknownResponse, }, }, }; @@ -173,8 +178,6 @@ pub fn map_response(frame: &Frame) -> Result } else { X0 + (raw - Y0) / K2 }; - // Clamping naturally absorbs noise-floor outliers (e.g. raw ≈ 0.385 - // resolves to well below -60 dB here, so it correctly floors to -60). db.clamp(MIN_DB, MAX_DB) } @@ -182,7 +185,7 @@ pub fn map_response(frame: &Frame) -> Result return Err(ProtocolError::new("Not enough bytes for meter levels")); } - let channels: Vec = (0..36) + let raw_meters: Vec = (0..36) .step_by(3) .enumerate() .map(|(index, i)| { @@ -206,7 +209,30 @@ pub fn map_response(frame: &Frame) -> Result }) .collect(); - return Ok(DeviceResponseType::Meters(Meters { channels })); + let mut input_iter = InputChannel::iter(); + let mut output_iter = OutputChannel::iter(); + + let mut input_meters = HashMap::new(); + let mut output_meters = HashMap::new(); + + for (index, meter) in raw_meters.into_iter().enumerate() { + if index < 4 { + if let Some(channel) = input_iter.next() { + input_meters.insert(channel, meter); + } + } else if let Some(channel) = output_iter.next() { + output_meters.insert(channel, meter); + } + } + + return Ok(DeviceResponseType::Meters( + InputMeters { + meters: input_meters, + }, + OutputMeters { + meters: output_meters, + }, + )); } Ok(DeviceResponseType::UnknownResponse(UnknownResponse { @@ -421,8 +447,6 @@ mod tests { #[test] fn meters_decode() { - // 38 bytes required. - // First 36 bytes are meter values, remaining 2 bytes ignored. let mut payload = vec![0; 38]; // half float 1.0 = 0x3c00 @@ -434,11 +458,12 @@ mod tests { let result = map_response(&frame(0x40, payload)).unwrap(); match result { - DeviceResponseType::Meters(m) => { - assert_eq!(m.channels.len(), 12); + DeviceResponseType::Meters(inputs, outputs) => { + assert_eq!(inputs.meters.len(), 4); + assert_eq!(outputs.meters.len(), 8); - for channel in m.channels { - assert!(channel.rms.is_finite()); + for meter in inputs.meters.values().chain(outputs.meters.values()) { + assert!(meter.rms.is_finite()); } } other => panic!("unexpected {:?}", other), diff --git a/src/dsp408/mod.rs b/src/dsp408/mod.rs index 1c1f95f..654c3c8 100644 --- a/src/dsp408/mod.rs +++ b/src/dsp408/mod.rs @@ -1,7 +1,9 @@ mod client; +mod default; mod mapper; mod protocol; pub mod types; pub use client::DSP408; +pub(crate) use default::default_dsp_state; diff --git a/src/dsp408/types.rs b/src/dsp408/types.rs index c88c09d..937af52 100644 --- a/src/dsp408/types.rs +++ b/src/dsp408/types.rs @@ -227,12 +227,6 @@ pub struct Meter { pub clip: bool, } -#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq)] -pub struct Meters { - pub channels: Vec, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct ConfigChunk { pub sub_index: u8, @@ -254,7 +248,7 @@ pub enum DeviceResponseType { PresetName(PresetName), DeviceFlags(DeviceFlags), AuthenticationResult(AuthenticationResult), - Meters(Meters), + Meters(InputMeters, OutputMeters), CurrentPreset(CurrentPreset), ModifiedPreset(ModifiedPreset), ConfigChunk(ConfigChunk), @@ -412,3 +406,23 @@ pub struct DSPState { pub(crate) presets: PresetBank, pub(crate) current_config: DSPConfigState, } + +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DSPMode { + Demo, + Disconnected, + Connected, +} + +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Default)] +pub struct InputMeters { + pub meters: HashMap, +} + +#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Default)] +pub struct OutputMeters { + pub meters: HashMap, +} diff --git a/src/main.rs b/src/main.rs index 5fc7684..8719dab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,15 @@ fn main() -> Result<(), Box> { // dsp.set_mute(types::Channel::Input(types::InputChannel::InA), true)?; - let r = dsp.set_peq_band(types::Channel::Input(InA), types::PEQBand::B1, 3.0, 1400.0, 2.1, types::PEQFilter::LowShelf, false)?; + let r = dsp.set_peq_band( + types::Channel::Input(InA), + types::PEQBand::B1, + 3.0, + 1400.0, + 2.1, + types::PEQFilter::LowShelf, + false, + )?; dbg!(r); // dbg!(dsp.state()?);