Compare commits
18 Commits
6e2283755a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dad09cddcf | |||
| 87921968b4 | |||
| 81cac2f239 | |||
| 89ff2827ff | |||
| 54f26a0dd2 | |||
| cc3ae754ff | |||
| 4d548a7973 | |||
| b57b85f959 | |||
| 7766d9b91d | |||
| b13e846fa5 | |||
| c37fa47b28 | |||
| 02145b6ef0 | |||
| 6429685cd2 | |||
| f1f769e0e6 | |||
| f468cb3c6d | |||
| 582d876abf | |||
| ac5c9eeaa0 | |||
| 822cdc1587 |
348
Cargo.lock
generated
348
Cargo.lock
generated
@ -127,6 +127,28 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags 2.11.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa-sys"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad7569085a265dd3f607ebecce7458eaab2132a84393534c95b18dcbc3f31e04"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.6.0"
|
||||
@ -409,6 +431,33 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bfsk-modem"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cpal",
|
||||
"eframe",
|
||||
"egui",
|
||||
"egui_plot",
|
||||
"hound",
|
||||
"num",
|
||||
"oxydsp-dsp",
|
||||
"oxydsp-flowgraph",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bfsk-modem-tx"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cpal",
|
||||
"hound",
|
||||
"num",
|
||||
"oxydsp-dsp",
|
||||
"oxydsp-flowgraph",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
@ -451,6 +500,15 @@ dependencies = [
|
||||
"objc2 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
||||
dependencies = [
|
||||
"objc2 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.6.2"
|
||||
@ -592,6 +650,17 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "5.4.1"
|
||||
@ -692,6 +761,59 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coreaudio-rs"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d15c3c3cee7c087938f7ad1c3098840b3ef1f1bdc7f6e496336c3b1e7a6f3914"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"libc",
|
||||
"objc2-audio-toolbox",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpal"
|
||||
version = "0.17.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8942da362c0f0d895d7cac616263f2f9424edc5687364dfd1d25ef7eba506d7"
|
||||
dependencies = [
|
||||
"alsa",
|
||||
"coreaudio-rs",
|
||||
"dasp_sample",
|
||||
"jni 0.21.1",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"mach2",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"objc2 0.6.4",
|
||||
"objc2-audio-toolbox",
|
||||
"objc2-avf-audio",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows 0.61.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
@ -701,6 +823,25 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
@ -719,6 +860,12 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
||||
|
||||
[[package]]
|
||||
name = "dasp_sample"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
@ -776,6 +923,21 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
|
||||
|
||||
[[package]]
|
||||
name = "dpsk-modem"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cpal",
|
||||
"eframe",
|
||||
"egui",
|
||||
"egui_plot",
|
||||
"hound",
|
||||
"num",
|
||||
"oxydsp-dsp",
|
||||
"oxydsp-flowgraph",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ecolor"
|
||||
version = "0.33.3"
|
||||
@ -1012,19 +1174,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
"egui_plot",
|
||||
"hound",
|
||||
"num",
|
||||
"oxydsp-dsp",
|
||||
"oxydsp-flowgraph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
@ -1210,6 +1359,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"rand_core",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
@ -1741,6 +1891,15 @@ version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
@ -1904,6 +2063,17 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
@ -2008,7 +2178,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"libc",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-data",
|
||||
@ -2030,6 +2200,31 @@ dependencies = [
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-audio-toolbox"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"libc",
|
||||
"objc2 0.6.4",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-avf-audio"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
|
||||
dependencies = [
|
||||
"objc2 0.6.4",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-cloud-kit"
|
||||
version = "0.2.2"
|
||||
@ -2037,7 +2232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation 0.2.2",
|
||||
@ -2049,11 +2244,34 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-audio"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
|
||||
dependencies = [
|
||||
"dispatch2",
|
||||
"objc2 0.6.4",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-audio-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"objc2 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.2.2"
|
||||
@ -2061,7 +2279,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
@ -2073,7 +2291,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2 0.6.2",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
"objc2 0.6.4",
|
||||
]
|
||||
|
||||
@ -2096,7 +2316,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-metal",
|
||||
@ -2108,7 +2328,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-contacts",
|
||||
"objc2-foundation 0.2.2",
|
||||
@ -2127,7 +2347,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"dispatch",
|
||||
"libc",
|
||||
"objc2 0.5.2",
|
||||
@ -2140,6 +2360,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2 0.6.2",
|
||||
"libc",
|
||||
"objc2 0.6.4",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
@ -2161,7 +2383,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
@ -2174,7 +2396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
@ -2186,7 +2408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-metal",
|
||||
@ -2209,7 +2431,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
@ -2229,7 +2451,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
@ -2241,7 +2463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation 0.2.2",
|
||||
@ -2298,12 +2520,14 @@ dependencies = [
|
||||
"num",
|
||||
"oxydsp-flowgraph",
|
||||
"rustfft",
|
||||
"wide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxydsp-flowgraph"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"oxydsp-flowgraph-macros",
|
||||
]
|
||||
|
||||
@ -2510,6 +2734,22 @@ version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
|
||||
|
||||
[[package]]
|
||||
name = "qpsk-modem"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cpal",
|
||||
"eframe",
|
||||
"egui",
|
||||
"egui_plot",
|
||||
"hound",
|
||||
"num",
|
||||
"oxydsp-dsp",
|
||||
"oxydsp-flowgraph",
|
||||
"rand",
|
||||
"rand_distr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
@ -2556,6 +2796,33 @@ version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom 0.4.2",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
|
||||
|
||||
[[package]]
|
||||
name = "rand_distr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "range-alloc"
|
||||
version = "0.1.5"
|
||||
@ -2668,6 +2935,15 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "safe_arch"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f7caad094bd561859bcd467734a720c3c1f5d1f338995351fefe2190c45efed"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@ -2800,6 +3076,14 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "simple"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"oxydsp-dsp",
|
||||
"oxydsp-flowgraph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
@ -3665,6 +3949,16 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wide"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "198f6abc41fab83526d10880fa5c17e2b4ee44e763949b4bb34e2fd1e8ca48e4"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"safe_arch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
@ -4096,7 +4390,7 @@ dependencies = [
|
||||
"android-activity",
|
||||
"atomic-waker",
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"bytemuck",
|
||||
"calloop 0.13.0",
|
||||
"cfg_aliases",
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@ -1,3 +1,12 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["example", "oxydsp-dsp","oxydsp-flowgraph"]
|
||||
members = [
|
||||
"examples/*",
|
||||
"oxydsp-dsp",
|
||||
"oxydsp-flowgraph"
|
||||
]
|
||||
|
||||
[profile.release-with-debug]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
|
||||
|
||||
BIN
example/mod.wav
BIN
example/mod.wav
Binary file not shown.
@ -1,21 +0,0 @@
|
||||
|
||||
digraph G {
|
||||
node [shape=record];
|
||||
rankdir=TB;
|
||||
IterSource_0 [label="{ IterSource |{<o0> output} }"];
|
||||
Map_1 [label="{ {<i0> input}| Map |{<o0> output} }"];
|
||||
Repeat_2 [label="{ {<i0> input}| Repeat |{<o0> output} }"];
|
||||
Nco_3 [label="{ {<i0> frequency}| Nco |{<o0> output} }"];
|
||||
OscillatorSource_4 [label="{ OscillatorSource |{<o0> output} }"];
|
||||
Multiplier_5 [label="{ {<i0> input_a|<i1> input_b}| Multiplier |{<o0> output} }"];
|
||||
TxSink_6 [label="{ {<i0> input}| TxSink }"];
|
||||
|
||||
IterSource_0:o0 -> Map_1:i0 [label="bool"];
|
||||
Map_1:o0 -> Repeat_2:i0 [label="oxydsp_dsp::units::DigitalFrequency"];
|
||||
Repeat_2:o0 -> Nco_3:i0 [label="oxydsp_dsp::units::DigitalFrequency"];
|
||||
Nco_3:o0 -> Multiplier_5:i0 [label="num_complex::Complex<f32>"];
|
||||
OscillatorSource_4:o0 -> Multiplier_5:i1 [label="num_complex::Complex<f32>"];
|
||||
Multiplier_5:o0 -> TxSink_6:i0 [label="num_complex::Complex<f32>"];
|
||||
|
||||
}
|
||||
|
||||
@ -1,225 +0,0 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::sync::mpmc::sync_channel;
|
||||
use std::sync::mpsc;
|
||||
|
||||
use eframe::NativeOptions;
|
||||
use egui_plot::Line;
|
||||
use egui_plot::PlotPoints;
|
||||
use num::Complex;
|
||||
use oxydsp_dsp::blocks::math::basic::Multiplier;
|
||||
use oxydsp_dsp::blocks::synthesis::Nco;
|
||||
use oxydsp_dsp::blocks::synthesis::OscillatorSource;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Map;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Repeat;
|
||||
use oxydsp_dsp::blocks::utilities::channels::TxSink;
|
||||
use oxydsp_dsp::blocks::utilities::iter::IterSource;
|
||||
use oxydsp_dsp::units::DigitalFrequency;
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::block::BlockResult;
|
||||
use oxydsp_flowgraph::block::SyncBlock;
|
||||
use oxydsp_flowgraph::block::SyncBlockIO;
|
||||
use oxydsp_flowgraph::flowgraph;
|
||||
use oxydsp_flowgraph::graph::FlowGraph;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use oxydsp_flowgraph::io::PopIterable;
|
||||
use oxydsp_flowgraph::sync_block;
|
||||
|
||||
use crate::printer_synchronous_block::PrinterView;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
#[sync_block]
|
||||
pub struct Printer<T: 'static>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[input]
|
||||
input_b: In<u32>,
|
||||
|
||||
#[output]
|
||||
output_a: Out<u32>,
|
||||
|
||||
n: usize,
|
||||
}
|
||||
|
||||
impl<'view, T> SyncBlock<'view> for Printer<T>
|
||||
{
|
||||
fn sync_work(state: Self::StateView, input: Self::Input) -> Option<Self::Output>
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Block for Printer<T>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
let state = PrinterView {
|
||||
n: &mut self.n,
|
||||
_sync_block_phantom: Default::default(),
|
||||
};
|
||||
|
||||
let output_a_write = self.output_a.write();
|
||||
(&mut self.input, &mut self.input_b).pop_iter().for_each(
|
||||
|((input_el, input_tag), (input_b_el, input_b_tag))| {
|
||||
let new_tag = Tag::from([input_tag, input_b_tag]);
|
||||
let output_a_el = <Self as SyncBlock>::sync_work(state, (input_el, input_b_el));
|
||||
|
||||
output_a_write.push((output_a_el, new_tag));
|
||||
},
|
||||
);
|
||||
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
// mod printer_synchronous_block
|
||||
// {
|
||||
// struct PrinterView<'view, T>
|
||||
// {
|
||||
// n: &'view mut usize,
|
||||
// _sync_block_phantom: std::marker::PhantomData<'view, T>,
|
||||
// }
|
||||
// impl<'view, T: 'static> oxydsp_flowgraph::block::SyncBlockIO<'view> for super::Printer<T>
|
||||
// {
|
||||
// type StateView = PrinterView<'view, T>;
|
||||
// type Input = ();
|
||||
// type Output = ();
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<T: 'static> Printer<T>
|
||||
{
|
||||
pub fn new(input: In<T>) -> Self
|
||||
{
|
||||
Self { input, n: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Block for Printer<T>
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
for x in self.input.pop_iter()
|
||||
{
|
||||
if self.n.is_multiple_of(2usize.pow(20))
|
||||
{
|
||||
println!("{}", x);
|
||||
self.n = 0;
|
||||
}
|
||||
self.n += 1;
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
fn main()
|
||||
{
|
||||
let sample_rate = 48_000;
|
||||
let sample_per_symbol = 96;
|
||||
let deviation = DigitalFrequency::from_time_frequency(500., sample_rate as f64);
|
||||
let carrier = DigitalFrequency::from_time_frequency(1000., sample_rate as f64);
|
||||
|
||||
let data = (0..255u8).flat_map(to_bits).collect::<Vec<_>>();
|
||||
let (bit_stream, bits) = IterSource::new(data.into_iter());
|
||||
let (to_freq, freq) = Map::new(bits, move |x| [-deviation, deviation][x as usize]);
|
||||
let (repeat, freq) = Repeat::new(freq, sample_per_symbol);
|
||||
let (base_oscillator, baseband) = Nco::<f32>::new(freq);
|
||||
let (local_oscillator, lo) = OscillatorSource::<f32>::new(carrier.into());
|
||||
let (frontend, passband) = Multiplier::new(baseband, lo);
|
||||
let (tx, rx) = mpsc::channel::<Complex<f32>>();
|
||||
let sink = TxSink::new(passband, tx);
|
||||
|
||||
let graph = flowgraph![
|
||||
bit_stream,
|
||||
to_freq,
|
||||
repeat,
|
||||
base_oscillator,
|
||||
local_oscillator,
|
||||
frontend,
|
||||
sink,
|
||||
];
|
||||
File::create("out.dot")
|
||||
.unwrap()
|
||||
.write_all(graph.get_dot().as_bytes())
|
||||
.unwrap();
|
||||
let j = graph.run();
|
||||
let mut output = vec![];
|
||||
while let Ok(x) = rx.recv()
|
||||
{
|
||||
output.push(x);
|
||||
}
|
||||
let _ = j.join();
|
||||
|
||||
// Write signal
|
||||
let spec = hound::WavSpec {
|
||||
channels: 1,
|
||||
sample_rate,
|
||||
bits_per_sample: 16,
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
let mut writer = hound::WavWriter::create("mod.wav", spec).unwrap();
|
||||
for x in output.iter()
|
||||
{
|
||||
let amplitude = i16::MAX as f32;
|
||||
writer.write_sample((x.re * amplitude) as i16).unwrap();
|
||||
}
|
||||
writer.finalize().unwrap();
|
||||
//
|
||||
|
||||
eframe::run_simple_native("Plot", NativeOptions::default(), move |ctx, _frame| {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui_plot::Plot::new("hello").show(ui, |plot_ui| {
|
||||
plot_ui.line(Line::new(
|
||||
"samples",
|
||||
output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, s)| [i as f64, s.re as f64])
|
||||
.collect::<PlotPoints>(),
|
||||
));
|
||||
});
|
||||
ctx.request_repaint();
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn to_bits(n: u8) -> [bool; 8]
|
||||
{
|
||||
[
|
||||
(n & 1) == 1,
|
||||
(n >> 1) & 1 == 1,
|
||||
(n >> 2) & 1 == 1,
|
||||
(n >> 3) & 1 == 1,
|
||||
(n >> 4) & 1 == 1,
|
||||
(n >> 5) & 1 == 1,
|
||||
(n >> 6) & 1 == 1,
|
||||
(n >> 7) & 1 == 1,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn from_bits(n: [bool; 8]) -> u8
|
||||
{
|
||||
(n[0] as u8)
|
||||
| ((n[1] as u8) << 1)
|
||||
| ((n[2] as u8) << 2)
|
||||
| ((n[3] as u8) << 3)
|
||||
| ((n[4] as u8) << 4)
|
||||
| ((n[5] as u8) << 5)
|
||||
| ((n[6] as u8) << 6)
|
||||
| ((n[7] as u8) << 7)
|
||||
}
|
||||
|
||||
pub fn gaussian(sigma: f32, t: f32) -> f32
|
||||
{
|
||||
let sq = (t - 0.5) / sigma;
|
||||
(-sq * sq).exp()
|
||||
}
|
||||
12
examples/bfsk-modem-tx/Cargo.toml
Normal file
12
examples/bfsk-modem-tx/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "bfsk-modem-tx"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
oxydsp-flowgraph = {path = "../../oxydsp-flowgraph/"}
|
||||
oxydsp-dsp = {path = "../../oxydsp-dsp/"}
|
||||
num = "0.4.3"
|
||||
hound = "3.5.1"
|
||||
rand = "0.10.0"
|
||||
cpal = "0.17.3"
|
||||
BIN
examples/bfsk-modem-tx/mod.wav
Normal file
BIN
examples/bfsk-modem-tx/mod.wav
Normal file
Binary file not shown.
25
examples/bfsk-modem-tx/out.dot
Normal file
25
examples/bfsk-modem-tx/out.dot
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
digraph G {
|
||||
node [shape=record];
|
||||
rankdir=TB;
|
||||
IterSource_0 [label="{ IterSource |{<o0> output} }"];
|
||||
FirFilter_1 [label="{ {<i0> input}| FirFilter |{<o0> output} }"];
|
||||
Map_2 [label="{ {<i0> input}| Map |{<o0> output} }"];
|
||||
Repeat_3 [label="{ {<i0> input}| Repeat |{<o0> output} }"];
|
||||
Nco_4 [label="{ {<i0> frequency}| Nco |{<o0> output} }"];
|
||||
OscillatorSource_5 [label="{ OscillatorSource |{<o0> output} }"];
|
||||
Multiplier_6 [label="{ {<i0> input_a|<i1> input_b}| Multiplier |{<o0> output} }"];
|
||||
MapResultTagged_7 [label="{ {<i0> input}| MapResultTagged |{<o0> output} }"];
|
||||
NullSink_8 [label="{ {<i0> input}| NullSink }"];
|
||||
|
||||
IterSource_0:o0 -> Repeat_3:i0 [label="f32"];
|
||||
FirFilter_1:o0 -> Map_2:i0 [label="f32"];
|
||||
Map_2:o0 -> Nco_4:i0 [label="oxydsp_dsp::units::DigitalFrequency"];
|
||||
Repeat_3:o0 -> FirFilter_1:i0 [label="f32"];
|
||||
Nco_4:o0 -> Multiplier_6:i0 [label="num_complex::Complex<f32>"];
|
||||
OscillatorSource_5:o0 -> Multiplier_6:i1 [label="num_complex::Complex<f32>"];
|
||||
Multiplier_6:o0 -> MapResultTagged_7:i0 [label="num_complex::Complex<f32>"];
|
||||
MapResultTagged_7:o0 -> NullSink_8:i0 [label="num_complex::Complex<f32>"];
|
||||
|
||||
}
|
||||
|
||||
BIN
examples/bfsk-modem-tx/output.wav
Normal file
BIN
examples/bfsk-modem-tx/output.wav
Normal file
Binary file not shown.
54
examples/bfsk-modem-tx/src/main.rs
Normal file
54
examples/bfsk-modem-tx/src/main.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use crate::transmitter::Transmitter;
|
||||
|
||||
pub mod transmitter;
|
||||
|
||||
fn main()
|
||||
{
|
||||
println!("Transmitter");
|
||||
let tx = Transmitter::start_new();
|
||||
|
||||
loop
|
||||
{
|
||||
let mut user_input = String::new();
|
||||
std::io::stdin().read_line(&mut user_input).unwrap();
|
||||
println!("Transmitting ...");
|
||||
tx.transmit(user_input.as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
pub const SAMPLE_RATE: usize = 48_000;
|
||||
pub const SAMPLE_PER_SYMBOL: usize = 48;
|
||||
pub const DEVIATION: f64 = 500.;
|
||||
pub const CARRIER: f64 = 1700.;
|
||||
|
||||
pub fn to_bits(n: u8) -> [bool; 8]
|
||||
{
|
||||
[
|
||||
(n & 1) == 1,
|
||||
(n >> 1) & 1 == 1,
|
||||
(n >> 2) & 1 == 1,
|
||||
(n >> 3) & 1 == 1,
|
||||
(n >> 4) & 1 == 1,
|
||||
(n >> 5) & 1 == 1,
|
||||
(n >> 6) & 1 == 1,
|
||||
(n >> 7) & 1 == 1,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn from_bits(n: [bool; 8]) -> u8
|
||||
{
|
||||
(n[0] as u8)
|
||||
| ((n[1] as u8) << 1)
|
||||
| ((n[2] as u8) << 2)
|
||||
| ((n[3] as u8) << 3)
|
||||
| ((n[4] as u8) << 4)
|
||||
| ((n[5] as u8) << 5)
|
||||
| ((n[6] as u8) << 6)
|
||||
| ((n[7] as u8) << 7)
|
||||
}
|
||||
|
||||
pub fn gaussian(sigma: f32, t: f32) -> f32
|
||||
{
|
||||
let sq = (t - 0.5) / sigma;
|
||||
(-sq * sq).exp()
|
||||
}
|
||||
207
examples/bfsk-modem-tx/src/transmitter.rs
Normal file
207
examples/bfsk-modem-tx/src/transmitter.rs
Normal file
@ -0,0 +1,207 @@
|
||||
use cpal::Stream;
|
||||
use cpal::traits::DeviceTrait;
|
||||
use cpal::traits::HostTrait;
|
||||
use num::Complex;
|
||||
use oxydsp_dsp::blocks::filtering::fir::FirFilter;
|
||||
use oxydsp_dsp::blocks::math::basic::Multiplier;
|
||||
use oxydsp_dsp::blocks::synthesis::Nco;
|
||||
use oxydsp_dsp::blocks::synthesis::OscillatorSource;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::FlatMap;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Map;
|
||||
use oxydsp_dsp::blocks::utilities::channels::RxSource;
|
||||
use oxydsp_dsp::blocks::utilities::channels::TxSink;
|
||||
use oxydsp_dsp::filtering::fir::Fir;
|
||||
use oxydsp_dsp::units::DigitalFrequency;
|
||||
use oxydsp_flowgraph::flowgraph;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use rand::random;
|
||||
use std::f32::consts::PI;
|
||||
use std::ops::BitXor;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::mpsc::SyncSender;
|
||||
use std::sync::mpsc::sync_channel;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use crate::CARRIER;
|
||||
use crate::DEVIATION;
|
||||
use crate::SAMPLE_PER_SYMBOL;
|
||||
use crate::SAMPLE_RATE;
|
||||
use crate::to_bits;
|
||||
|
||||
pub struct Transmitter
|
||||
{
|
||||
flowgraph_handle: JoinHandle<()>,
|
||||
packet_sender: SyncSender<Vec<u8>>,
|
||||
stream: Stream,
|
||||
}
|
||||
|
||||
impl Transmitter
|
||||
{
|
||||
pub fn start_new() -> Self
|
||||
{
|
||||
let carrier = DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64);
|
||||
let deviation = DigitalFrequency::from_time_frequency(DEVIATION, SAMPLE_RATE as f64);
|
||||
|
||||
let (packet_tx, packet_rx): (_, Receiver<Vec<u8>>) = sync_channel(128);
|
||||
let (packet_rec, packets): (_, In<Vec<u8>>) = RxSource::new(packet_rx);
|
||||
let (linearizer, bits) = FlatMap::new(packets, |packet| {
|
||||
// +1 for chksum
|
||||
let packet_length = packet.len() as u16;
|
||||
let checksum = packet.iter().copied().reduce(BitXor::bitxor).unwrap();
|
||||
|
||||
// Learning sequence
|
||||
let mut frame = vec![0b10101010; 8];
|
||||
// Preamble
|
||||
frame.push(0b01100111);
|
||||
frame.push(packet_length.to_le_bytes()[0]);
|
||||
frame.push(packet_length.to_le_bytes()[1]);
|
||||
frame.extend(packet.iter());
|
||||
frame.push(checksum);
|
||||
frame.extend((0..16).map(|_| 0));
|
||||
frame
|
||||
.into_iter()
|
||||
.flat_map(to_bits)
|
||||
.map(|x| if x { 1. } else { -1. })
|
||||
});
|
||||
|
||||
let (repeat, bits) = FlatMap::new(bits, |symbol| {
|
||||
let mut v = vec![0.; SAMPLE_PER_SYMBOL - 1];
|
||||
v.push(symbol);
|
||||
v
|
||||
});
|
||||
|
||||
// gaussian fir
|
||||
// let fir = Fir((0..SAMPLE_PER_SYMBOL)
|
||||
// .map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32))
|
||||
// .collect());
|
||||
//.normalized();
|
||||
|
||||
// RRC fir
|
||||
let rrc_symbol_count = 4;
|
||||
let rrc_fir_length = SAMPLE_PER_SYMBOL * rrc_symbol_count;
|
||||
let fir = Fir((0..rrc_fir_length)
|
||||
.map(|x| {
|
||||
let centered = oxydsp_dsp::map(
|
||||
x as f32,
|
||||
0.,
|
||||
rrc_fir_length as f32,
|
||||
-(rrc_symbol_count as f32) * 0.5,
|
||||
rrc_symbol_count as f32 * 0.5,
|
||||
);
|
||||
root_raised_cosine(centered, 1., 1.)
|
||||
})
|
||||
.collect());
|
||||
|
||||
let (bit_filter, bits) = FirFilter::new(bits, fir);
|
||||
let (to_freq, freq) = Map::new(bits, move |x| {
|
||||
DigitalFrequency::from_time_frequency(DEVIATION * x as f64, SAMPLE_RATE as f64)
|
||||
});
|
||||
let (base_oscillator, baseband) = Nco::<f32>::new(freq);
|
||||
let (local_oscillator, lo) = OscillatorSource::<f32>::new(carrier.into());
|
||||
let (frontend, passband) = Multiplier::new(baseband, lo);
|
||||
let (audio_tx, audio_rx) = mpsc::channel::<Complex<f32>>();
|
||||
|
||||
let reverb_length = 200;
|
||||
// let (reverb, passband) = FirFilter::new(
|
||||
// passband,
|
||||
// Fir((0..reverb_length)
|
||||
// .map(|x| (-5. * (x as f32) / (reverb_length as f32)).exp())
|
||||
// .collect())
|
||||
// .normalized(),
|
||||
// );
|
||||
//
|
||||
let (awgn, passband) = Map::new(passband, |x| {
|
||||
x + Complex::<f32>::new(2. * (random::<f32>() - 0.5), 2. * (random::<f32>() - 0.5))
|
||||
* 0.0
|
||||
});
|
||||
|
||||
let tx_sink = TxSink::new(passband, audio_tx);
|
||||
|
||||
let graph = flowgraph![
|
||||
packet_rec,
|
||||
linearizer,
|
||||
//reverb,
|
||||
bit_filter,
|
||||
//udp_map,
|
||||
to_freq,
|
||||
repeat,
|
||||
base_oscillator,
|
||||
local_oscillator,
|
||||
frontend,
|
||||
awgn,
|
||||
tx_sink,
|
||||
];
|
||||
|
||||
// Open output device
|
||||
let host = cpal::default_host();
|
||||
let device = host
|
||||
.default_output_device()
|
||||
.expect("no output device available");
|
||||
let mut supported_configs_range = device
|
||||
.supported_output_configs()
|
||||
.expect("error while querying configs");
|
||||
let supported_config = supported_configs_range
|
||||
.next()
|
||||
.expect("no supported config?!")
|
||||
.with_sample_rate(SAMPLE_RATE as u32);
|
||||
let stream = device
|
||||
.build_output_stream(
|
||||
&supported_config.into(),
|
||||
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
||||
for x in data.iter_mut()
|
||||
{
|
||||
if let Ok(y) = audio_rx.try_recv()
|
||||
{
|
||||
*x = y.re * 0.01;
|
||||
}
|
||||
else
|
||||
{
|
||||
*x = 0.;
|
||||
}
|
||||
}
|
||||
},
|
||||
move |err| panic!(),
|
||||
None, // None=blocking, Some(Duration)=timeout
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
flowgraph_handle: graph.run(),
|
||||
packet_sender: packet_tx,
|
||||
stream,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transmit(&self, data: Vec<u8>)
|
||||
{
|
||||
let _ = self.packet_sender.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_raised_cosine(t: f32, beta: f32, ts: f32) -> f32
|
||||
{
|
||||
let eps = 1e-8;
|
||||
|
||||
if t.abs() < eps
|
||||
{
|
||||
// t = 0 special case
|
||||
return (1.0 / ts.sqrt()) * (1.0 + beta * (4.0 / PI - 1.0));
|
||||
}
|
||||
|
||||
if beta > 0.0 && (t.abs() - ts / (4.0 * beta)).abs() < eps
|
||||
{
|
||||
// t = ±T / (4β) special case
|
||||
let term1 = (1.0 + 2.0 / PI) * (PI / (4.0 * beta)).sin();
|
||||
let term2 = (1.0 - 2.0 / PI) * (PI / (4.0 * beta)).cos();
|
||||
return (beta / (ts.sqrt() * 2.0_f32.sqrt())) * (term1 + term2);
|
||||
}
|
||||
|
||||
// General case
|
||||
let numerator = (PI * t * (1.0 - beta) / ts).sin()
|
||||
+ 4.0 * beta * t / ts * (PI * t * (1.0 + beta) / ts).cos();
|
||||
|
||||
let denominator = PI * t * (1.0 - (4.0 * beta * t / ts).powi(2)) / ts;
|
||||
|
||||
(1.0 / ts.sqrt()) * (numerator / denominator)
|
||||
}
|
||||
1
examples/bfsk-modem/.gitignore
vendored
Normal file
1
examples/bfsk-modem/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
||||
@ -1,13 +1,15 @@
|
||||
[package]
|
||||
name = "example"
|
||||
name = "bfsk-modem"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
oxydsp-flowgraph = {path = "../oxydsp-flowgraph/"}
|
||||
oxydsp-dsp = {path = "../oxydsp-dsp/"}
|
||||
oxydsp-flowgraph = {path = "../../oxydsp-flowgraph/"}
|
||||
oxydsp-dsp = {path = "../../oxydsp-dsp/"}
|
||||
egui = "0.33.3"
|
||||
egui_plot = "0.34.1"
|
||||
eframe = { version = "0.33.3", features = ["default_fonts", "wayland"] }
|
||||
num = "0.4.3"
|
||||
hound = "3.5.1"
|
||||
rand = "0.10.0"
|
||||
cpal = "0.17.3"
|
||||
BIN
examples/bfsk-modem/mod.wav
Normal file
BIN
examples/bfsk-modem/mod.wav
Normal file
Binary file not shown.
25
examples/bfsk-modem/out.dot
Normal file
25
examples/bfsk-modem/out.dot
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
digraph G {
|
||||
node [shape=record];
|
||||
rankdir=TB;
|
||||
IterSource_0 [label="{ IterSource |{<o0> output} }"];
|
||||
FirFilter_1 [label="{ {<i0> input}| FirFilter |{<o0> output} }"];
|
||||
Map_2 [label="{ {<i0> input}| Map |{<o0> output} }"];
|
||||
Repeat_3 [label="{ {<i0> input}| Repeat |{<o0> output} }"];
|
||||
Nco_4 [label="{ {<i0> frequency}| Nco |{<o0> output} }"];
|
||||
OscillatorSource_5 [label="{ OscillatorSource |{<o0> output} }"];
|
||||
Multiplier_6 [label="{ {<i0> input_a|<i1> input_b}| Multiplier |{<o0> output} }"];
|
||||
MapResultTagged_7 [label="{ {<i0> input}| MapResultTagged |{<o0> output} }"];
|
||||
NullSink_8 [label="{ {<i0> input}| NullSink }"];
|
||||
|
||||
IterSource_0:o0 -> Repeat_3:i0 [label="f32"];
|
||||
FirFilter_1:o0 -> Map_2:i0 [label="f32"];
|
||||
Map_2:o0 -> Nco_4:i0 [label="oxydsp_dsp::units::DigitalFrequency"];
|
||||
Repeat_3:o0 -> FirFilter_1:i0 [label="f32"];
|
||||
Nco_4:o0 -> Multiplier_6:i0 [label="num_complex::Complex<f32>"];
|
||||
OscillatorSource_5:o0 -> Multiplier_6:i1 [label="num_complex::Complex<f32>"];
|
||||
Multiplier_6:o0 -> MapResultTagged_7:i0 [label="num_complex::Complex<f32>"];
|
||||
MapResultTagged_7:o0 -> NullSink_8:i0 [label="num_complex::Complex<f32>"];
|
||||
|
||||
}
|
||||
|
||||
BIN
examples/bfsk-modem/output.wav
Normal file
BIN
examples/bfsk-modem/output.wav
Normal file
Binary file not shown.
64
examples/bfsk-modem/src/main.rs
Normal file
64
examples/bfsk-modem/src/main.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::receiver::RadioReceiver;
|
||||
use crate::transmitter::Transmitter;
|
||||
|
||||
pub mod receiver;
|
||||
pub mod transmitter;
|
||||
|
||||
fn main()
|
||||
{
|
||||
if std::env::args().len() == 2
|
||||
{
|
||||
println!("Transmitter");
|
||||
let tx = Transmitter::start_new();
|
||||
|
||||
loop
|
||||
{
|
||||
let mut user_input = String::new();
|
||||
std::io::stdin().read_line(&mut user_input).unwrap();
|
||||
println!("Transmitting ...");
|
||||
tx.transmit(user_input.as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
println!("Receiver");
|
||||
let _tx = RadioReceiver::start_new();
|
||||
}
|
||||
}
|
||||
|
||||
pub const SAMPLE_RATE: usize = 48_000;
|
||||
pub const SAMPLE_PER_SYMBOL: usize = 48;
|
||||
pub const DEVIATION: f64 = 500.;
|
||||
pub const CARRIER: f64 = 1700.;
|
||||
|
||||
pub fn to_bits(n: u8) -> [bool; 8]
|
||||
{
|
||||
[
|
||||
(n & 1) == 1,
|
||||
(n >> 1) & 1 == 1,
|
||||
(n >> 2) & 1 == 1,
|
||||
(n >> 3) & 1 == 1,
|
||||
(n >> 4) & 1 == 1,
|
||||
(n >> 5) & 1 == 1,
|
||||
(n >> 6) & 1 == 1,
|
||||
(n >> 7) & 1 == 1,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn from_bits(n: [bool; 8]) -> u8
|
||||
{
|
||||
(n[0] as u8)
|
||||
| ((n[1] as u8) << 1)
|
||||
| ((n[2] as u8) << 2)
|
||||
| ((n[3] as u8) << 3)
|
||||
| ((n[4] as u8) << 4)
|
||||
| ((n[5] as u8) << 5)
|
||||
| ((n[6] as u8) << 6)
|
||||
| ((n[7] as u8) << 7)
|
||||
}
|
||||
|
||||
pub fn gaussian(sigma: f32, t: f32) -> f32
|
||||
{
|
||||
let sq = (t - 0.5) / sigma;
|
||||
(-sq * sq).exp()
|
||||
}
|
||||
369
examples/bfsk-modem/src/receiver.rs
Normal file
369
examples/bfsk-modem/src/receiver.rs
Normal file
@ -0,0 +1,369 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::net::UdpSocket;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use cpal::Stream;
|
||||
use cpal::traits::DeviceTrait;
|
||||
use cpal::traits::HostTrait;
|
||||
use eframe::NativeOptions;
|
||||
use egui::Color32;
|
||||
use egui_plot::Line;
|
||||
use egui_plot::PlotPoints;
|
||||
use num::Complex;
|
||||
use num::Zero;
|
||||
use oxydsp_dsp::blocks::filtering::fir::FirFilter;
|
||||
use oxydsp_dsp::blocks::iq::zero_if::ZeroIf;
|
||||
use oxydsp_dsp::blocks::ted::early_late::EarlyLateGate;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Map;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::NullSink;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Scan;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::ScanTagged;
|
||||
use oxydsp_dsp::blocks::utilities::channels::RxSource;
|
||||
use oxydsp_dsp::blocks::utilities::squelch::Squelch;
|
||||
use oxydsp_dsp::filtering::fir::Fir;
|
||||
use oxydsp_dsp::units::DigitalFrequency;
|
||||
use oxydsp_flowgraph::flowgraph;
|
||||
use oxydsp_flowgraph::graph::FlowGraph;
|
||||
use oxydsp_flowgraph::tag::Tag;
|
||||
use oxydsp_flowgraph::tag::Tagged;
|
||||
use oxydsp_flowgraph::tag::Tags;
|
||||
|
||||
use crate::CARRIER;
|
||||
use crate::DEVIATION;
|
||||
use crate::SAMPLE_PER_SYMBOL;
|
||||
use crate::SAMPLE_RATE;
|
||||
use crate::gaussian;
|
||||
use crate::transmitter::root_raised_cosine;
|
||||
|
||||
pub enum PacketBuilderBitState
|
||||
{
|
||||
WaitingForPreamble,
|
||||
InPacket,
|
||||
}
|
||||
|
||||
pub enum PacketBuilderByteState
|
||||
{
|
||||
Length1,
|
||||
Length2,
|
||||
Data,
|
||||
}
|
||||
|
||||
pub struct PacketBuilder
|
||||
{
|
||||
current_byte: u8,
|
||||
bit_index: u8,
|
||||
|
||||
bit_state: PacketBuilderBitState,
|
||||
packet_state: PacketBuilderByteState,
|
||||
|
||||
// Packet building
|
||||
length: u16,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PacketBuilder
|
||||
{
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
current_byte: 0,
|
||||
bit_index: 0,
|
||||
bit_state: PacketBuilderBitState::WaitingForPreamble,
|
||||
packet_state: PacketBuilderByteState::Length1,
|
||||
length: 0,
|
||||
data: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn next_byte(&mut self) -> Option<Vec<u8>>
|
||||
{
|
||||
match self.packet_state
|
||||
{
|
||||
PacketBuilderByteState::Length1 =>
|
||||
{
|
||||
self.length = 0;
|
||||
self.length |= self.current_byte as u16;
|
||||
println!("starting packet, length 1 {}", self.current_byte);
|
||||
self.packet_state = PacketBuilderByteState::Length2;
|
||||
}
|
||||
PacketBuilderByteState::Length2 =>
|
||||
{
|
||||
println!("starting packet, length 2 {}", self.current_byte);
|
||||
self.length |= (self.current_byte as u16) << 8;
|
||||
self.data = vec![];
|
||||
self.packet_state = PacketBuilderByteState::Data;
|
||||
println!("length : {}", self.length);
|
||||
}
|
||||
PacketBuilderByteState::Data =>
|
||||
{
|
||||
self.data.push(self.current_byte);
|
||||
self.length -= 1;
|
||||
if self.length == 0
|
||||
{
|
||||
println!("finished");
|
||||
let current = std::mem::replace(self, Self::new());
|
||||
return Some(current.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn next_bit(&mut self, bit: bool) -> Option<Vec<u8>>
|
||||
{
|
||||
self.current_byte >>= 1;
|
||||
self.current_byte |= (bit as u8) << 7;
|
||||
match self.bit_state
|
||||
{
|
||||
PacketBuilderBitState::WaitingForPreamble =>
|
||||
{
|
||||
if self.current_byte == 0b01100111
|
||||
{
|
||||
println!("preamble heard !");
|
||||
self.bit_state = PacketBuilderBitState::InPacket;
|
||||
self.bit_index = 0;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
PacketBuilderBitState::InPacket =>
|
||||
{
|
||||
self.bit_index += 1;
|
||||
if self.bit_index == 8
|
||||
{
|
||||
let out = self.next_byte();
|
||||
self.bit_index = 0;
|
||||
return out;
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RadioReceiver {
|
||||
//stream: Stream,
|
||||
//pub packet_receiver: Receiver<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl RadioReceiver
|
||||
{
|
||||
pub fn start_new() -> Self
|
||||
{
|
||||
let carrier = DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64);
|
||||
let mut tags = Tags::default();
|
||||
|
||||
let (audio_tx, audio_rx) = mpsc::channel();
|
||||
let (packet_tx, packet_rx) = mpsc::channel::<Vec<u8>>();
|
||||
let (source, signal) = RxSource::new(audio_rx);
|
||||
let (inspect, signal) = Map::new(signal, |x| {
|
||||
//println!("{x}");
|
||||
x
|
||||
});
|
||||
let (mut zero_if, iq) = ZeroIf::new(signal, carrier.into());
|
||||
|
||||
zero_if.set_fir(Fir::lowpass(
|
||||
DigitalFrequency::from_time_frequency(2. * DEVIATION + 100., SAMPLE_RATE as f64),
|
||||
SAMPLE_PER_SYMBOL * 4,
|
||||
));
|
||||
|
||||
let (squelch, iq) = Squelch::new(iq, 5., 100);
|
||||
|
||||
let (arg_extract, arg) = Scan::new(iq, Complex::zero(), |state, sample| {
|
||||
let angle: Complex<f32> = sample / *state;
|
||||
*state = sample;
|
||||
angle.arg()
|
||||
/ DigitalFrequency::from_time_frequency(DEVIATION, SAMPLE_RATE as f64).as_rad()
|
||||
as f32
|
||||
});
|
||||
|
||||
// gaussian fir
|
||||
let fir = Fir((0..SAMPLE_PER_SYMBOL)
|
||||
.map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32))
|
||||
.collect())
|
||||
.normalized();
|
||||
|
||||
// RRC fir
|
||||
let rrc_symbol_count = 4;
|
||||
let rrc_fir_length = SAMPLE_PER_SYMBOL * rrc_symbol_count;
|
||||
let fir = Fir((0..rrc_fir_length)
|
||||
.map(|x| {
|
||||
let centered = oxydsp_dsp::map(
|
||||
x as f32,
|
||||
0.,
|
||||
rrc_fir_length as f32,
|
||||
-(rrc_symbol_count as f32) * 0.5,
|
||||
rrc_symbol_count as f32 * 0.5,
|
||||
);
|
||||
root_raised_cosine(centered, 1., 1.)
|
||||
})
|
||||
.collect())
|
||||
.normalized();
|
||||
|
||||
let (matched_filter, arg) = FirFilter::new(arg, fir);
|
||||
let mut elg_loop = Fir(vec![1. / 30.; 30]);
|
||||
*elg_loop.0.last_mut().unwrap() = 0.3;
|
||||
let symbol_tag = tags.allocate_tag("early late gate symbol");
|
||||
let (elg, arg) = EarlyLateGate::new(arg, elg_loop, SAMPLE_PER_SYMBOL, symbol_tag.clone());
|
||||
|
||||
// // Eye diagram
|
||||
let (tx, rx) = mpsc::channel::<(Vec<f32>, f32)>();
|
||||
//let (eye_sender, arg) = ScanTagged::new(arg, VecDeque::<()>::new(), move |history, x| {
|
||||
let symbol_tag2 = symbol_tag.clone();
|
||||
let (eye_sender, arg) = ScanTagged::new(arg, VecDeque::new(), move |history, x| {
|
||||
let cloned_tag = x.1.clone();
|
||||
if history.len() == 2 * SAMPLE_PER_SYMBOL
|
||||
{
|
||||
history.pop_back();
|
||||
}
|
||||
|
||||
let mut error: f32 = 0.;
|
||||
let is_symbol_center = x.1.as_ref().is_some_and(|t| {
|
||||
if let Some(err) = t.retrieve(&symbol_tag2.clone())
|
||||
{
|
||||
error = *err;
|
||||
true
|
||||
}
|
||||
else
|
||||
{
|
||||
false
|
||||
}
|
||||
});
|
||||
history.push_front(((is_symbol_center, error), x.0));
|
||||
|
||||
if history.len() > SAMPLE_PER_SYMBOL && history[SAMPLE_PER_SYMBOL].0.0
|
||||
{
|
||||
let _ = tx.send((
|
||||
history.iter().map(|(_, x)| *x).collect::<Vec<_>>(),
|
||||
history[SAMPLE_PER_SYMBOL].0.1,
|
||||
));
|
||||
}
|
||||
|
||||
Tagged::new(x.0, None)
|
||||
});
|
||||
|
||||
let (packet_map, arg) =
|
||||
ScanTagged::new(arg, PacketBuilder::new(), move |builder, sample| {
|
||||
if sample
|
||||
.1
|
||||
.as_ref()
|
||||
.is_some_and(|t| t.retrieve(&symbol_tag.clone()).is_some())
|
||||
&& let Some(packet) = builder.next_bit(sample.0 < 0.)
|
||||
{
|
||||
let _ = packet_tx.send(packet);
|
||||
}
|
||||
|
||||
Tagged::new(sample.0, None)
|
||||
});
|
||||
let null_sink = NullSink::new(arg);
|
||||
|
||||
let graph = flowgraph![
|
||||
source,
|
||||
inspect,
|
||||
squelch,
|
||||
zero_if,
|
||||
packet_map,
|
||||
arg_extract,
|
||||
//sig_lowpass,
|
||||
matched_filter,
|
||||
elg,
|
||||
eye_sender,
|
||||
null_sink
|
||||
];
|
||||
let t = graph.run();
|
||||
|
||||
// Setup input
|
||||
// let host = cpal::default_host();
|
||||
// let device = host.default_input_device().expect("No input device");
|
||||
// let mut supported_configs_range = device
|
||||
// .supported_input_configs()
|
||||
// .expect("error while querying configs");
|
||||
// let supported_config = supported_configs_range
|
||||
// .next()
|
||||
// .expect("no supported config?!")
|
||||
// .with_sample_rate(SAMPLE_RATE as u32);
|
||||
// let stream = device
|
||||
// .build_input_stream(
|
||||
// &supported_config.into(),
|
||||
// move |data: &[f32], _: &cpal::InputCallbackInfo| {
|
||||
// for x in data.iter()
|
||||
// {
|
||||
// let _ = audio_tx.send(*x);
|
||||
// }
|
||||
// },
|
||||
// move |err| {
|
||||
// panic!() // react to errors here.
|
||||
// },
|
||||
// None, // None=blocking, Some(Duration)=timeout
|
||||
// )
|
||||
// .unwrap();
|
||||
std::thread::spawn(move || {
|
||||
let socket = UdpSocket::bind("0.0.0.0:25565").unwrap();
|
||||
|
||||
let mut buffer = [0u8; 4096];
|
||||
while let Ok(read) = socket.recv(&mut buffer)
|
||||
{
|
||||
let read_buffer = &mut buffer[0..read];
|
||||
for x in read_buffer.chunks(4)
|
||||
{
|
||||
let val = f32::from_le_bytes([x[0], x[1], x[2], x[3]]);
|
||||
let _ = audio_tx.send(val);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut eyes = VecDeque::new();
|
||||
eframe::run_simple_native("Plot", NativeOptions::default(), move |ctx, _frame| {
|
||||
while let Ok(x) = packet_rx.try_recv()
|
||||
{
|
||||
println!("Got data: {} bytes.", x.len());
|
||||
let str: String = x.iter().map(|x| *x as char).collect();
|
||||
println!("-----\n\n{}\n\n-----", str);
|
||||
}
|
||||
|
||||
while let Ok(eye) = rx.try_recv()
|
||||
{
|
||||
if eyes.len() >= 100
|
||||
{
|
||||
let _ = eyes.pop_back();
|
||||
}
|
||||
eyes.push_front(eye);
|
||||
}
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui_plot::Plot::new("hello").show(ui, |plot_ui| {
|
||||
for eye in eyes.iter()
|
||||
{
|
||||
plot_ui.line(
|
||||
Line::new(
|
||||
"eyes",
|
||||
eye.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, s)| [i as f64 / 2., *s as f64])
|
||||
.collect::<PlotPoints>(),
|
||||
)
|
||||
.id("eyes")
|
||||
.color(Color32::GREEN),
|
||||
);
|
||||
}
|
||||
});
|
||||
ctx.request_repaint();
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
//stream,
|
||||
//packet_receiver: packet_rx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color_from_err(error: f32, max: f32) -> Color32
|
||||
{
|
||||
Color32::RED
|
||||
.linear_multiply(error.abs() / max)
|
||||
.blend(Color32::GREEN.linear_multiply((1. - error.abs() / max).max(0.)))
|
||||
}
|
||||
228
examples/bfsk-modem/src/transmitter.rs
Normal file
228
examples/bfsk-modem/src/transmitter.rs
Normal file
@ -0,0 +1,228 @@
|
||||
use cpal::Stream;
|
||||
use cpal::traits::DeviceTrait;
|
||||
use cpal::traits::HostTrait;
|
||||
use num::Complex;
|
||||
use oxydsp_dsp::blocks::filtering::fir::FirFilter;
|
||||
use oxydsp_dsp::blocks::math::basic::Multiplier;
|
||||
use oxydsp_dsp::blocks::synthesis::Nco;
|
||||
use oxydsp_dsp::blocks::synthesis::OscillatorSource;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::FlatMap;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Map;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Repeat;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Scan;
|
||||
use oxydsp_dsp::blocks::utilities::channels::RxSource;
|
||||
use oxydsp_dsp::blocks::utilities::channels::TxSink;
|
||||
use oxydsp_dsp::filtering::fir::Fir;
|
||||
use oxydsp_dsp::units::DigitalFrequency;
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::block::BlockResult;
|
||||
use oxydsp_flowgraph::flowgraph;
|
||||
use oxydsp_flowgraph::graph::FlowGraph;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use rand::random;
|
||||
use std::f32::consts::PI;
|
||||
use std::iter::FusedIterator;
|
||||
use std::net::UdpSocket;
|
||||
use std::ops::BitXor;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::mpsc::SyncSender;
|
||||
use std::sync::mpsc::sync_channel;
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::CARRIER;
|
||||
use crate::DEVIATION;
|
||||
use crate::SAMPLE_PER_SYMBOL;
|
||||
use crate::SAMPLE_RATE;
|
||||
use crate::gaussian;
|
||||
use crate::to_bits;
|
||||
|
||||
pub struct Transmitter
|
||||
{
|
||||
flowgraph_handle: JoinHandle<()>,
|
||||
packet_sender: SyncSender<Vec<u8>>,
|
||||
stream: Stream,
|
||||
}
|
||||
|
||||
impl Transmitter
|
||||
{
|
||||
pub fn start_new() -> Self
|
||||
{
|
||||
let carrier = DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64);
|
||||
let deviation = DigitalFrequency::from_time_frequency(DEVIATION, SAMPLE_RATE as f64);
|
||||
|
||||
let (packet_tx, packet_rx): (_, Receiver<Vec<u8>>) = sync_channel(128);
|
||||
let (packet_rec, packets): (_, In<Vec<u8>>) = RxSource::new(packet_rx);
|
||||
let (linearizer, bits) = FlatMap::new(packets, |packet| {
|
||||
// +1 for chksum
|
||||
let packet_length = packet.len() as u16;
|
||||
let checksum = packet.iter().copied().reduce(BitXor::bitxor).unwrap();
|
||||
|
||||
// Learning sequence
|
||||
let mut frame = vec![0b10101010; 8];
|
||||
// Preamble
|
||||
frame.push(0b01100111);
|
||||
frame.push(packet_length.to_le_bytes()[0]);
|
||||
frame.push(packet_length.to_le_bytes()[1]);
|
||||
frame.extend(packet.iter());
|
||||
frame.push(checksum);
|
||||
frame.extend((0..16).map(|_| 0));
|
||||
frame
|
||||
.into_iter()
|
||||
.flat_map(to_bits)
|
||||
.map(|x| if x { 1. } else { -1. })
|
||||
});
|
||||
|
||||
let (repeat, bits) = FlatMap::new(bits, |symbol| {
|
||||
let mut v = vec![0.; SAMPLE_PER_SYMBOL - 1];
|
||||
v.push(symbol);
|
||||
v
|
||||
});
|
||||
|
||||
// gaussian fir
|
||||
// let fir = Fir((0..SAMPLE_PER_SYMBOL)
|
||||
// .map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32))
|
||||
// .collect());
|
||||
//.normalized();
|
||||
|
||||
// RRC fir
|
||||
let rrc_symbol_count = 4;
|
||||
let rrc_fir_length = SAMPLE_PER_SYMBOL * rrc_symbol_count;
|
||||
let fir = Fir((0..rrc_fir_length)
|
||||
.map(|x| {
|
||||
let centered = oxydsp_dsp::map(
|
||||
x as f32,
|
||||
0.,
|
||||
rrc_fir_length as f32,
|
||||
-(rrc_symbol_count as f32) * 0.5,
|
||||
rrc_symbol_count as f32 * 0.5,
|
||||
);
|
||||
root_raised_cosine(centered, 1., 1.)
|
||||
})
|
||||
.collect());
|
||||
|
||||
let (bit_filter, bits) = FirFilter::new(bits, fir);
|
||||
let (to_freq, freq) = Map::new(bits, move |x| {
|
||||
DigitalFrequency::from_time_frequency(DEVIATION * x as f64, SAMPLE_RATE as f64)
|
||||
});
|
||||
let (base_oscillator, baseband) = Nco::<f32>::new(freq);
|
||||
let (local_oscillator, lo) = OscillatorSource::<f32>::new(carrier.into());
|
||||
let (frontend, passband) = Multiplier::new(baseband, lo);
|
||||
let (audio_tx, audio_rx) = mpsc::channel::<Complex<f32>>();
|
||||
|
||||
let reverb_length = 200;
|
||||
let (reverb, passband) = FirFilter::new(
|
||||
passband,
|
||||
Fir((0..reverb_length)
|
||||
.map(|x| (-5. * (x as f32) / (reverb_length as f32)).exp())
|
||||
.collect())
|
||||
.normalized(),
|
||||
);
|
||||
|
||||
let (awgn, passband) = Map::new(passband, |x| {
|
||||
x + Complex::<f32>::new(2. * (random::<f32>() - 0.5), 2. * (random::<f32>() - 0.5))
|
||||
* 0.3
|
||||
});
|
||||
|
||||
let (udp_map, passband) = Scan::new(
|
||||
passband,
|
||||
UdpSocket::bind("0.0.0.0:0").unwrap(),
|
||||
|sckt, sample| {
|
||||
std::thread::sleep(Duration::from_micros(12));
|
||||
sckt.send_to(&(sample.re).to_le_bytes(), "127.0.0.1:25565")
|
||||
.unwrap();
|
||||
sample
|
||||
},
|
||||
);
|
||||
let tx_sink = TxSink::new(passband, audio_tx);
|
||||
|
||||
let graph = flowgraph![
|
||||
packet_rec,
|
||||
linearizer,
|
||||
reverb,
|
||||
bit_filter,
|
||||
udp_map,
|
||||
to_freq,
|
||||
repeat,
|
||||
base_oscillator,
|
||||
local_oscillator,
|
||||
frontend,
|
||||
awgn,
|
||||
tx_sink,
|
||||
];
|
||||
|
||||
// Open output device
|
||||
let host = cpal::default_host();
|
||||
let device = host
|
||||
.default_output_device()
|
||||
.expect("no output device available");
|
||||
let mut supported_configs_range = device
|
||||
.supported_output_configs()
|
||||
.expect("error while querying configs");
|
||||
let supported_config = supported_configs_range
|
||||
.next()
|
||||
.expect("no supported config?!")
|
||||
.with_sample_rate(SAMPLE_RATE as u32);
|
||||
let stream = device
|
||||
.build_output_stream(
|
||||
&supported_config.into(),
|
||||
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
||||
for x in data.iter_mut()
|
||||
{
|
||||
if let Ok(y) = audio_rx.try_recv()
|
||||
{
|
||||
*x = y.re * 0.01;
|
||||
}
|
||||
else
|
||||
{
|
||||
*x = 0.;
|
||||
}
|
||||
}
|
||||
},
|
||||
move |err| panic!(),
|
||||
None, // None=blocking, Some(Duration)=timeout
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
flowgraph_handle: graph.run(),
|
||||
packet_sender: packet_tx,
|
||||
stream,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transmit(&self, data: Vec<u8>)
|
||||
{
|
||||
let _ = self.packet_sender.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_raised_cosine(t: f32, beta: f32, ts: f32) -> f32
|
||||
{
|
||||
let eps = 1e-8;
|
||||
|
||||
if t.abs() < eps
|
||||
{
|
||||
// t = 0 special case
|
||||
return (1.0 / ts.sqrt()) * (1.0 + beta * (4.0 / PI - 1.0));
|
||||
}
|
||||
|
||||
if beta > 0.0 && (t.abs() - ts / (4.0 * beta)).abs() < eps
|
||||
{
|
||||
// t = ±T / (4β) special case
|
||||
let term1 = (1.0 + 2.0 / PI) * (PI / (4.0 * beta)).sin();
|
||||
let term2 = (1.0 - 2.0 / PI) * (PI / (4.0 * beta)).cos();
|
||||
return (beta / (ts.sqrt() * 2.0_f32.sqrt())) * (term1 + term2);
|
||||
}
|
||||
|
||||
// General case
|
||||
let numerator = (PI * t * (1.0 - beta) / ts).sin()
|
||||
+ 4.0 * beta * t / ts * (PI * t * (1.0 + beta) / ts).cos();
|
||||
|
||||
let denominator = PI * t * (1.0 - (4.0 * beta * t / ts).powi(2)) / ts;
|
||||
|
||||
(1.0 / ts.sqrt()) * (numerator / denominator)
|
||||
}
|
||||
15
examples/dpsk-modem/Cargo.toml
Normal file
15
examples/dpsk-modem/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "dpsk-modem"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
oxydsp-flowgraph = {path = "../../oxydsp-flowgraph/"}
|
||||
oxydsp-dsp = {path = "../../oxydsp-dsp/"}
|
||||
egui = "0.33.3"
|
||||
egui_plot = "0.34.1"
|
||||
eframe = { version = "0.33.3", features = ["default_fonts", "wayland"] }
|
||||
num = "0.4.3"
|
||||
hound = "3.5.1"
|
||||
rand = "0.10.0"
|
||||
cpal = "0.17.3"
|
||||
26
examples/dpsk-modem/demodulator.dot
Normal file
26
examples/dpsk-modem/demodulator.dot
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
digraph G {
|
||||
node [shape=record];
|
||||
rankdir=TB;
|
||||
IterSource_0 [label="{ IterSource |{<o0> output} }"];
|
||||
ZeroIf_1 [label="{ {<i0> input}| ZeroIf |{<o0> output} }"];
|
||||
NullSink_2 [label="{ {<i0> input}| NullSink }"];
|
||||
Scan_3 [label="{ {<i0> input}| Scan |{<o0> output} }"];
|
||||
FirFilter_4 [label="{ {<i0> input}| FirFilter |{<o0> output} }"];
|
||||
Tee_5 [label="{ {<i0> input}| Tee |{<o0> output_a|<o1> output_b} }"];
|
||||
Map_6 [label="{ {<i0> input}| Map |{<o0> output} }"];
|
||||
Scan_7 [label="{ {<i0> input}| Scan |{<o0> output} }"];
|
||||
Multiplier_8 [label="{ {<i0> input_a|<i1> input_b}| Multiplier |{<o0> output} }"];
|
||||
|
||||
IterSource_0:o0 -> ZeroIf_1:i0 [label="f32"];
|
||||
ZeroIf_1:o0 -> Tee_5:i0 [label="num_complex::Complex<f32>"];
|
||||
Scan_3:o0 -> NullSink_2:i0 [label="()"];
|
||||
FirFilter_4:o0 -> Scan_3:i0 [label="f32"];
|
||||
Tee_5:o0 -> Multiplier_8:i0 [label="num_complex::Complex<f32>"];
|
||||
Tee_5:o1 -> Scan_7:i0 [label="num_complex::Complex<f32>"];
|
||||
Map_6:o0 -> FirFilter_4:i0 [label="f32"];
|
||||
Scan_7:o0 -> Multiplier_8:i1 [label="num_complex::Complex<f32>"];
|
||||
Multiplier_8:o0 -> Map_6:i0 [label="num_complex::Complex<f32>"];
|
||||
|
||||
}
|
||||
|
||||
BIN
examples/dpsk-modem/mod.wav
Normal file
BIN
examples/dpsk-modem/mod.wav
Normal file
Binary file not shown.
BIN
examples/dpsk-modem/output.wav
Normal file
BIN
examples/dpsk-modem/output.wav
Normal file
Binary file not shown.
425
examples/dpsk-modem/src/main.rs
Normal file
425
examples/dpsk-modem/src/main.rs
Normal file
@ -0,0 +1,425 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::env::args;
|
||||
use std::f32::consts::PI;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::UdpSocket;
|
||||
use std::os::unix::thread;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::mpsc::sync_channel;
|
||||
use std::time::Duration;
|
||||
|
||||
use cpal::traits::DeviceTrait;
|
||||
use cpal::traits::HostTrait;
|
||||
use eframe::NativeOptions;
|
||||
use egui::Color32;
|
||||
use egui_plot::Line;
|
||||
use egui_plot::PlotPoints;
|
||||
use egui_plot::Points;
|
||||
use num::Complex;
|
||||
use num::Integer;
|
||||
use oxydsp_dsp::blocks::filtering::fir::FirFilter;
|
||||
use oxydsp_dsp::blocks::iq::zero_if::ZeroIf;
|
||||
use oxydsp_dsp::blocks::math::basic;
|
||||
use oxydsp_dsp::blocks::math::basic::Multiplier;
|
||||
use oxydsp_dsp::blocks::synthesis::OscillatorSource;
|
||||
use oxydsp_dsp::blocks::ted::early_late::EarlyLateGate;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::FlatMap;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Map;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::MapResultTagged;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::NullSink;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Repeat;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Scan;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::ScanTagged;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Tee;
|
||||
use oxydsp_dsp::blocks::utilities::channels::RxSource;
|
||||
use oxydsp_dsp::blocks::utilities::channels::TxSink;
|
||||
use oxydsp_dsp::blocks::utilities::iter::IterSource;
|
||||
use oxydsp_dsp::filtering::fir::Fir;
|
||||
use oxydsp_dsp::units::DigitalFrequency;
|
||||
use oxydsp_flowgraph::block::BlockResult;
|
||||
use oxydsp_flowgraph::flowgraph;
|
||||
use oxydsp_flowgraph::io::AnonymousIn;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::tag::Tags;
|
||||
use rand::random;
|
||||
|
||||
const SAMPLE_RATE: usize = 48_000;
|
||||
const CARRIER: f64 = 1000.;
|
||||
const SAMPLE_PER_SYMBOL: usize = 48;
|
||||
|
||||
fn main()
|
||||
{
|
||||
let args = std::env::args();
|
||||
if args.len() == 1
|
||||
{
|
||||
demodulator();
|
||||
}
|
||||
else
|
||||
{
|
||||
modulator();
|
||||
}
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
fn demodulator()
|
||||
{
|
||||
let (signal_tx, signal_rx) = channel();
|
||||
// std::thread::spawn(move || {
|
||||
// let udp_socket = UdpSocket::bind("0.0.0.0:25565").unwrap();
|
||||
// let mut buffer = [0u8; 4096];
|
||||
// while let Ok(size) = udp_socket.recv(&mut buffer)
|
||||
// {
|
||||
// let read = &mut buffer[..size];
|
||||
// for bytes in read.chunks(4)
|
||||
// {
|
||||
// if bytes.len() == 4
|
||||
// {
|
||||
// let _ = signal_tx
|
||||
// .send(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// Open output device
|
||||
let host = cpal::default_host();
|
||||
let device = host
|
||||
.default_input_device()
|
||||
.expect("no output device available");
|
||||
let mut supported_configs_range = device
|
||||
.supported_input_configs()
|
||||
.expect("error while querying configs");
|
||||
let supported_config = supported_configs_range
|
||||
.next()
|
||||
.expect("no supported config?!")
|
||||
.with_sample_rate(SAMPLE_RATE as u32);
|
||||
let stream = device
|
||||
.build_input_stream(
|
||||
&supported_config.into(),
|
||||
move |data: &[f32], _: &cpal::InputCallbackInfo| {
|
||||
for x in data.iter()
|
||||
{
|
||||
let _ = signal_tx.send(*x);
|
||||
}
|
||||
},
|
||||
move |err| panic!(),
|
||||
None, // None=blocking, Some(Duration)=timeout
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (signal_source, signal) = IterSource::new(signal_rx.into_iter());
|
||||
let (mut zero_if, baseband): (_, In<Complex<f32>>) = ZeroIf::new(
|
||||
signal,
|
||||
DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64).into(),
|
||||
);
|
||||
zero_if.set_fir(Fir::lowpass(
|
||||
DigitalFrequency::from_time_frequency(CARRIER + 100., SAMPLE_RATE as f64),
|
||||
100,
|
||||
));
|
||||
|
||||
// Pulse shaping
|
||||
// gaussian fir
|
||||
let fir = Fir((0..SAMPLE_PER_SYMBOL)
|
||||
.map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32))
|
||||
.collect())
|
||||
.normalized();
|
||||
|
||||
// RRC fir
|
||||
let rrc_symbol_count = 4;
|
||||
let rrc_fir_length = SAMPLE_PER_SYMBOL * rrc_symbol_count;
|
||||
let fir = Fir((0..rrc_fir_length)
|
||||
.map(|x| {
|
||||
let centered = oxydsp_dsp::map(
|
||||
x as f32,
|
||||
0.,
|
||||
rrc_fir_length as f32,
|
||||
-(rrc_symbol_count as f32) * 0.5,
|
||||
rrc_symbol_count as f32 * 0.5,
|
||||
);
|
||||
root_raised_cosine(centered, 1., 1.)
|
||||
})
|
||||
.collect())
|
||||
.normalized();
|
||||
|
||||
let (tee, b_a, b_b) = Tee::new(baseband);
|
||||
|
||||
let (delay, b_b) = Scan::new(
|
||||
b_b,
|
||||
VecDeque::from(vec![Complex::<f32>::new(0., 0.); SAMPLE_PER_SYMBOL]),
|
||||
|history, x| {
|
||||
history.push_back(x);
|
||||
history.pop_front().unwrap().conj()
|
||||
},
|
||||
);
|
||||
|
||||
let (mult, baseband) = Multiplier::new(b_a, b_b);
|
||||
let (to_arg, baseband) = Map::new(baseband, |x| (x * Complex::<f32>::new(0., -1.)).arg());
|
||||
let (phase_filter, baseband) = FirFilter::new(baseband, fir);
|
||||
|
||||
let (eye_tx, eye_rx) = channel::<Vec<f32>>();
|
||||
let (eye, baseband) = Scan::new(baseband, VecDeque::new(), move |state, x| {
|
||||
if state.len() >= SAMPLE_PER_SYMBOL * 2
|
||||
{
|
||||
//state.pop_front();
|
||||
eye_tx.send(Vec::from(state.clone()));
|
||||
state.clear();
|
||||
}
|
||||
state.push_back(x);
|
||||
});
|
||||
let null_sink = NullSink::new(baseband);
|
||||
|
||||
let graph = flowgraph![
|
||||
signal_source,
|
||||
zero_if,
|
||||
null_sink,
|
||||
eye,
|
||||
phase_filter,
|
||||
tee,
|
||||
to_arg,
|
||||
delay,
|
||||
mult
|
||||
];
|
||||
File::create("demodulator.dot")
|
||||
.unwrap()
|
||||
.write_all(graph.get_dot().as_bytes())
|
||||
.unwrap();
|
||||
graph.run();
|
||||
|
||||
let mut constellation = VecDeque::new();
|
||||
eframe::run_simple_native("Plot", NativeOptions::default(), move |ctx, _frame| {
|
||||
while let Ok(sample) = eye_rx.try_recv()
|
||||
{
|
||||
if constellation.len() >= 300
|
||||
{
|
||||
let _ = constellation.pop_back();
|
||||
}
|
||||
|
||||
constellation.push_front(sample);
|
||||
}
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui_plot::Plot::new("hello").show(ui, |plot_ui| {
|
||||
for eye in constellation.iter()
|
||||
{
|
||||
plot_ui.line(
|
||||
Line::new(
|
||||
"eye",
|
||||
eye.iter()
|
||||
.enumerate()
|
||||
.map(|(i, e)| [i as f64, *e as f64])
|
||||
.collect::<PlotPoints>(),
|
||||
)
|
||||
.color(Color32::GREEN),
|
||||
);
|
||||
}
|
||||
// plot_ui.points(
|
||||
// Points::new(
|
||||
// "constellation",
|
||||
// constellation
|
||||
// .iter()
|
||||
// .map(|s| [s.re as f64, s.im as f64])
|
||||
// .collect::<PlotPoints>(),
|
||||
// )
|
||||
// .id("constellation")
|
||||
// .color(Color32::GREEN),
|
||||
// );
|
||||
});
|
||||
ctx.request_repaint();
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn modulator()
|
||||
{
|
||||
let (data_tx, data_rx) = channel();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
loop
|
||||
{
|
||||
let mut str = String::new();
|
||||
let input = std::io::stdin().read_line(&mut str).unwrap();
|
||||
|
||||
for bit in str.as_bytes().iter().copied().flat_map(to_bits)
|
||||
{
|
||||
let _ = data_tx.send(bit);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut tags = Tags::new();
|
||||
let (bit_source, bits): (_, In<bool>) = RxSource::new(data_rx);
|
||||
//let last_tag = tags.allocate_tag("finished");
|
||||
|
||||
let (phase_map, phase) = Scan::new(bits, Complex::<f32>::new(1., 0.), |state, bit| {
|
||||
if bit
|
||||
{
|
||||
*state *= Complex::<f32>::new(-1., 0.);
|
||||
}
|
||||
*state
|
||||
});
|
||||
|
||||
// Convert to pulse train
|
||||
let (repeater, phase) = FlatMap::new(phase, |x| {
|
||||
let mut v = vec![Complex::<f32>::new(0., 0.); SAMPLE_PER_SYMBOL - 1];
|
||||
//let mut v = vec![x; SAMPLE_PER_SYMBOL - 1];
|
||||
v.push(x);
|
||||
v
|
||||
});
|
||||
|
||||
// RRC fir
|
||||
let rrc_symbol_count = 4;
|
||||
let rrc_fir_length = SAMPLE_PER_SYMBOL * rrc_symbol_count;
|
||||
let fir = Fir((0..rrc_fir_length)
|
||||
.map(|x| {
|
||||
let centered = oxydsp_dsp::map(
|
||||
x as f32,
|
||||
0.,
|
||||
rrc_fir_length as f32,
|
||||
-(rrc_symbol_count as f32) * 0.5,
|
||||
rrc_symbol_count as f32 * 0.5,
|
||||
);
|
||||
root_raised_cosine(centered, 1., 1.)
|
||||
})
|
||||
.collect());
|
||||
|
||||
let (phase_filter, phase) = FirFilter::new(phase, fir);
|
||||
|
||||
let (oscillator, passband) = OscillatorSource::<f32>::new(
|
||||
DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64).into(),
|
||||
);
|
||||
let (multiplier, passband) = Multiplier::new(passband, phase);
|
||||
|
||||
let reverb_length = 200;
|
||||
// let (reverb, passband) = FirFilter::new(
|
||||
// passband,
|
||||
// Fir((0..reverb_length)
|
||||
// .map(|x| (-20. * (x as f32) / (reverb_length as f32)).exp())
|
||||
// .collect())
|
||||
// .normalized(),
|
||||
// );
|
||||
|
||||
let (awgn, passband) = Map::new(passband, |x| {
|
||||
x + Complex::<f32>::new(2. * (random::<f32>() - 0.5), 2. * (random::<f32>() - 0.5)) * 0.0
|
||||
});
|
||||
|
||||
let (udp_map, passband) = Scan::new(
|
||||
passband,
|
||||
UdpSocket::bind("0.0.0.0:0").unwrap(),
|
||||
|sckt, sample| {
|
||||
std::thread::sleep(Duration::from_micros(15));
|
||||
sckt.send_to(&(sample.re).to_le_bytes(), "127.0.0.1:25565")
|
||||
.unwrap();
|
||||
sample
|
||||
},
|
||||
);
|
||||
let (audio_tx, audio_rx) = channel();
|
||||
let tx_sink = TxSink::new(passband, audio_tx);
|
||||
|
||||
let graph = flowgraph![
|
||||
bit_source,
|
||||
phase_map,
|
||||
repeater,
|
||||
oscillator,
|
||||
multiplier,
|
||||
tx_sink, //reverb,
|
||||
awgn,
|
||||
udp_map,
|
||||
phase_filter
|
||||
];
|
||||
|
||||
// Open output device
|
||||
let host = cpal::default_host();
|
||||
let device = host
|
||||
.default_output_device()
|
||||
.expect("no output device available");
|
||||
let mut supported_configs_range = device
|
||||
.supported_output_configs()
|
||||
.expect("error while querying configs");
|
||||
let supported_config = supported_configs_range
|
||||
.next()
|
||||
.expect("no supported config?!")
|
||||
.with_sample_rate(SAMPLE_RATE as u32);
|
||||
let stream = device
|
||||
.build_output_stream(
|
||||
&supported_config.into(),
|
||||
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
||||
for x in data.iter_mut()
|
||||
{
|
||||
if let Ok(y) = audio_rx.try_recv()
|
||||
{
|
||||
*x = y.re * 0.01;
|
||||
}
|
||||
else
|
||||
{
|
||||
*x = 0.;
|
||||
}
|
||||
}
|
||||
},
|
||||
move |err| panic!(),
|
||||
None, // None=blocking, Some(Duration)=timeout
|
||||
)
|
||||
.unwrap();
|
||||
let _ = graph.run().join();
|
||||
}
|
||||
|
||||
pub fn to_bits(n: u8) -> [bool; 8]
|
||||
{
|
||||
[
|
||||
(n & 1) == 1,
|
||||
(n >> 1) & 1 == 1,
|
||||
(n >> 2) & 1 == 1,
|
||||
(n >> 3) & 1 == 1,
|
||||
(n >> 4) & 1 == 1,
|
||||
(n >> 5) & 1 == 1,
|
||||
(n >> 6) & 1 == 1,
|
||||
(n >> 7) & 1 == 1,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn from_bits(n: [bool; 8]) -> u8
|
||||
{
|
||||
(n[0] as u8)
|
||||
| ((n[1] as u8) << 1)
|
||||
| ((n[2] as u8) << 2)
|
||||
| ((n[3] as u8) << 3)
|
||||
| ((n[4] as u8) << 4)
|
||||
| ((n[5] as u8) << 5)
|
||||
| ((n[6] as u8) << 6)
|
||||
| ((n[7] as u8) << 7)
|
||||
}
|
||||
|
||||
pub fn gaussian(sigma: f32, t: f32) -> f32
|
||||
{
|
||||
let sq = (t - 0.5) / sigma;
|
||||
(-sq * sq).exp()
|
||||
}
|
||||
|
||||
pub fn root_raised_cosine(t: f32, beta: f32, ts: f32) -> f32
|
||||
{
|
||||
let eps = 1e-8;
|
||||
|
||||
if t.abs() < eps
|
||||
{
|
||||
// t = 0 special case
|
||||
return (1.0 / ts.sqrt()) * (1.0 + beta * (4.0 / PI - 1.0));
|
||||
}
|
||||
|
||||
if beta > 0.0 && (t.abs() - ts / (4.0 * beta)).abs() < eps
|
||||
{
|
||||
// t = ±T / (4β) special case
|
||||
let term1 = (1.0 + 2.0 / PI) * (PI / (4.0 * beta)).sin();
|
||||
let term2 = (1.0 - 2.0 / PI) * (PI / (4.0 * beta)).cos();
|
||||
return (beta / (ts.sqrt() * 2.0_f32.sqrt())) * (term1 + term2);
|
||||
}
|
||||
|
||||
// General case
|
||||
let numerator = (PI * t * (1.0 - beta) / ts).sin()
|
||||
+ 4.0 * beta * t / ts * (PI * t * (1.0 + beta) / ts).cos();
|
||||
|
||||
let denominator = PI * t * (1.0 - (4.0 * beta * t / ts).powi(2)) / ts;
|
||||
|
||||
(1.0 / ts.sqrt()) * (numerator / denominator)
|
||||
}
|
||||
16
examples/qpsk-modem/Cargo.toml
Normal file
16
examples/qpsk-modem/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "qpsk-modem"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
oxydsp-flowgraph = {path = "../../oxydsp-flowgraph/"}
|
||||
oxydsp-dsp = {path = "../../oxydsp-dsp/"}
|
||||
egui = "0.33.3"
|
||||
egui_plot = "0.34.1"
|
||||
eframe = { version = "0.33.3", features = ["default_fonts", "wayland"] }
|
||||
num = "0.4.3"
|
||||
hound = "3.5.1"
|
||||
rand = "0.10.0"
|
||||
cpal = "0.17.3"
|
||||
rand_distr = "0.6.0"
|
||||
BIN
examples/qpsk-modem/mod.wav
Normal file
BIN
examples/qpsk-modem/mod.wav
Normal file
Binary file not shown.
431
examples/qpsk-modem/src/main.rs
Normal file
431
examples/qpsk-modem/src/main.rs
Normal file
@ -0,0 +1,431 @@
|
||||
|
||||
use cpal::traits::DeviceTrait;
|
||||
use cpal::traits::HostTrait;
|
||||
use egui::Color32;
|
||||
use egui_plot::Line;
|
||||
use egui_plot::PlotPoints;
|
||||
use egui_plot::Points;
|
||||
use num::Complex;
|
||||
use num::complex::ComplexFloat;
|
||||
use oxydsp_dsp::blocks::filtering::fir::FirFilter;
|
||||
use oxydsp_dsp::blocks::filtering::pulse_shaping::PulseShaper;
|
||||
use oxydsp_dsp::blocks::math::basic::Multiplier;
|
||||
use oxydsp_dsp::blocks::synthesis::OscillatorSource;
|
||||
use oxydsp_dsp::blocks::ted::early_late::EarlyLateGate;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Map;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Merger;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::NullSink;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Scan;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::ScanTagged;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Splitter;
|
||||
use oxydsp_dsp::blocks::utilities::channels::RxSource;
|
||||
use oxydsp_dsp::blocks::utilities::channels::TxSink;
|
||||
use oxydsp_dsp::blocks::utilities::graph_control::GraphKiller;
|
||||
use oxydsp_dsp::blocks::utilities::iter::IterSource;
|
||||
use oxydsp_dsp::filtering::fir::Fir;
|
||||
use oxydsp_dsp::filtering::history_buf::HistoryBuf;
|
||||
use oxydsp_dsp::map;
|
||||
use oxydsp_dsp::units::DigitalFrequency;
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::flowgraph;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use oxydsp_flowgraph::tag::Tagged;
|
||||
use oxydsp_flowgraph::tag::Tags;
|
||||
use rand::random;
|
||||
|
||||
const CARRRIER_FREQ: f64 = 1000.;
|
||||
const SAMPLE_RATE: usize = 48_000;
|
||||
const SAMPLE_PER_SYMBOL: usize = 100;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct CostasLoop
|
||||
{
|
||||
#[input]
|
||||
input: In<f32>,
|
||||
|
||||
#[output]
|
||||
output: Out<Complex<f32>>,
|
||||
|
||||
center_freq: DigitalFrequency,
|
||||
nco: oxydsp_dsp::synthesis::oscillator::Nco<f32>,
|
||||
|
||||
loop_filter: oxydsp_dsp::filtering::fir::FirFilter<f32, f32, f32>,
|
||||
low_pass: oxydsp_dsp::filtering::fir::FirFilter<Complex<f32>, Complex<f32>, Complex<f32>>,
|
||||
}
|
||||
|
||||
impl CostasLoop
|
||||
{
|
||||
pub fn new(
|
||||
input: In<f32>,
|
||||
start_frequency: DigitalFrequency,
|
||||
loop_filter: Fir<f32>,
|
||||
) -> (Self, In<Complex<f32>>)
|
||||
{
|
||||
let (output, iq) = oxydsp_flowgraph::io::stream();
|
||||
(
|
||||
CostasLoop {
|
||||
input,
|
||||
output,
|
||||
center_freq: start_frequency,
|
||||
nco: start_frequency.into(),
|
||||
loop_filter: oxydsp_dsp::filtering::fir::FirFilter::new(loop_filter),
|
||||
low_pass: oxydsp_dsp::filtering::fir::FirFilter::new(
|
||||
Fir::lowpass(start_frequency, 100).normalized_len(),
|
||||
),
|
||||
},
|
||||
iq,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Block for CostasLoop
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
self.output.push_iter(self.input.iter().map(|x| {
|
||||
let (signal, tag) = x.into();
|
||||
let lo = self.nco.next().unwrap();
|
||||
let iq = self
|
||||
.low_pass
|
||||
.next(Complex::new(lo.re * signal, lo.im * signal));
|
||||
|
||||
let error = iq.re * iq.im.signum() - iq.im * iq.re.signum();
|
||||
let correction = self.loop_filter.next(error);
|
||||
self.nco.set_frequency(DigitalFrequency::from_rad(
|
||||
self.center_freq.as_rad() + correction as f64,
|
||||
));
|
||||
|
||||
(iq, tag).into()
|
||||
}));
|
||||
oxydsp_flowgraph::block::BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main()
|
||||
{
|
||||
demodulator();
|
||||
//modulator();
|
||||
}
|
||||
|
||||
pub fn modulator()
|
||||
{
|
||||
let bit_source = (0..8192).map(|_| [random::<bool>(), random::<bool>()]);
|
||||
|
||||
let mut tags = Tags::default();
|
||||
|
||||
let (mut iter_source, bits) = IterSource::new(bit_source);
|
||||
let kill_tag = tags.allocate_tag("Kill tag");
|
||||
iter_source.tag_last_with(kill_tag.clone());
|
||||
|
||||
let (to_iq, iq) = Map::new(bits, |x| match x
|
||||
{
|
||||
[true, true] => Complex::new(1., 1.),
|
||||
[true, false] => Complex::new(1., -1.),
|
||||
[false, true] => Complex::new(-1., 1.),
|
||||
[false, false] => Complex::new(-1., -1.),
|
||||
});
|
||||
|
||||
// let (pulse_shaper, iq) = PulseShaper::new(
|
||||
// iq,
|
||||
// Fir::root_raised_cosine(4 * SAMPLE_PER_SYMBOL, 1., SAMPLE_PER_SYMBOL),
|
||||
// SAMPLE_PER_SYMBOL,
|
||||
// );
|
||||
let (pulse_shaper, iq) = PulseShaper::new(
|
||||
iq,
|
||||
Fir::gaussian(SAMPLE_PER_SYMBOL, 3.),
|
||||
SAMPLE_PER_SYMBOL,
|
||||
);
|
||||
let (carrier_oscillator, carrier) = OscillatorSource::new(
|
||||
DigitalFrequency::from_time_frequency(CARRRIER_FREQ, SAMPLE_RATE as f64).into(),
|
||||
);
|
||||
let (mixer, passband) = Multiplier::new(iq, carrier);
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel::<Complex<f32>>();
|
||||
let (graph_killer, passband) = GraphKiller::new(passband, kill_tag);
|
||||
let tx_sink = TxSink::new(passband, tx);
|
||||
|
||||
let graph = flowgraph![
|
||||
iter_source,
|
||||
to_iq,
|
||||
pulse_shaper,
|
||||
carrier_oscillator,
|
||||
mixer,
|
||||
graph_killer,
|
||||
tx_sink
|
||||
];
|
||||
graph.run(1);
|
||||
|
||||
let spec = hound::WavSpec {
|
||||
channels: 1,
|
||||
sample_rate: SAMPLE_RATE as u32,
|
||||
bits_per_sample: 16,
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
let mut writer = hound::WavWriter::create("mod.wav", spec).unwrap();
|
||||
for iq in rx.iter()
|
||||
{
|
||||
let amplitude = 0.5 * i16::MAX as f32;
|
||||
writer.write_sample((iq.re * amplitude) as i16).unwrap();
|
||||
}
|
||||
writer.finalize().unwrap();
|
||||
}
|
||||
|
||||
pub fn demodulator()
|
||||
{
|
||||
let (audio_tx, audio_rx) = std::sync::mpsc::channel();
|
||||
let (rx_source, signal) = RxSource::new(audio_rx);
|
||||
|
||||
let (downconverter, iq) = CostasLoop::new(
|
||||
signal,
|
||||
DigitalFrequency::from_time_frequency(CARRRIER_FREQ, SAMPLE_RATE as f64),
|
||||
Fir::proportional_integral(100, 0.01, 0.00005),
|
||||
);
|
||||
|
||||
//let mut agc_filter = oxydsp_dsp::filtering::fir::FirFilter::new(Fir::proportional_integral(100, 0.1, 0.001));
|
||||
let (agc_error_tx, agc_error_rx) = std::sync::mpsc::channel();
|
||||
let (agc, iq) = Scan::new(iq, (1., 0.), move |(gain, error_low), iq| {
|
||||
let out = *gain * iq;
|
||||
|
||||
let error = 1. - out.abs();
|
||||
let alpha = 0.01;
|
||||
*error_low = (1. - alpha) * *error_low + alpha * error;
|
||||
let _ = agc_error_tx.send(*error_low);
|
||||
*gain += 0.01 * error; // Feedback
|
||||
|
||||
out
|
||||
});
|
||||
|
||||
const DECIMATION: usize = 1;
|
||||
// let (matched_filter, iq) = FirFilter::<_, _, _, DECIMATION>::new_decimating(
|
||||
// iq,
|
||||
// Fir::<Complex<f32>>::lowpass(DigitalFrequency::from_time_frequency(2000., SAMPLE_RATE as f64), 100)
|
||||
// .normalized_len().convoluted_with(&Fir::<Complex<f32>>::root_raised_cosine(4 * SAMPLE_PER_SYMBOL, 1., SAMPLE_PER_SYMBOL)
|
||||
// .normalized_sqr())
|
||||
// );
|
||||
// let (matched_filter, iq) = FirFilter::<_, _, _, DECIMATION>::new_decimating(
|
||||
// iq,
|
||||
// Fir::<Complex<f32>>::lowpass(DigitalFrequency::from_time_frequency(2000., SAMPLE_RATE as f64), 100)
|
||||
// .normalized_len().convoluted_with(&Fir::<Complex<f32>>::gaussian(SAMPLE_PER_SYMBOL, 3.).normalized_sqr())
|
||||
// );
|
||||
let (matched_filter, iq) = FirFilter::<_, _, _, DECIMATION>::new_decimating(
|
||||
iq,
|
||||
Fir::<Complex<f32>>::gaussian(SAMPLE_PER_SYMBOL, 3.).normalized_sqr()
|
||||
);
|
||||
|
||||
let (splitter, [iq_i, iq_q]) = Splitter::new(iq);
|
||||
let (proj_i, i) = Map::new(iq_i, |x| x.re);
|
||||
let (proj_q, q) = Map::new(iq_q, |x| x.im);
|
||||
|
||||
let mut tags = Tags::default();
|
||||
|
||||
let elg_filter = Fir::proportional_integral(100, 0.2, 0.002);
|
||||
let i_key = tags.allocate_tag("i tag");
|
||||
let q_key = tags.allocate_tag("q tag");
|
||||
let (elg_i, i) = EarlyLateGate::new(i, elg_filter.clone(), SAMPLE_PER_SYMBOL / DECIMATION, i_key.clone());
|
||||
let (elg_q, q) = EarlyLateGate::new(q, elg_filter.clone(), SAMPLE_PER_SYMBOL / DECIMATION, q_key.clone());
|
||||
|
||||
let (eye_i_tx, eye_i_rx) = std::sync::mpsc::channel::<Vec<f32>>();
|
||||
let (eye_q_tx, eye_q_rx) = std::sync::mpsc::channel::<Vec<f32>>();
|
||||
|
||||
let (merger, iq) = Merger::new([i, q]);
|
||||
|
||||
let (constellation_tx, constellation_rx) = std::sync::mpsc::channel();
|
||||
|
||||
let (debug, iq) = ScanTagged::new(
|
||||
iq,
|
||||
(
|
||||
HistoryBuf::new(0., (SAMPLE_PER_SYMBOL * 2) / DECIMATION),
|
||||
HistoryBuf::new(0., (SAMPLE_PER_SYMBOL * 2) / DECIMATION),
|
||||
),
|
||||
move |(buf_i, buf_q), input| {
|
||||
let ([re, im], tag) = input.into();
|
||||
buf_i.push(re);
|
||||
buf_q.push(im);
|
||||
|
||||
if tag.is_some_and(|t| t.retrieve(&i_key).is_some())
|
||||
{
|
||||
let _ = constellation_tx.send(Complex::new(re, im));
|
||||
let _ = eye_i_tx.send(buf_i.as_slice().iter().copied().collect::<Vec<_>>());
|
||||
let _ = eye_q_tx.send(buf_q.as_slice().iter().copied().collect::<Vec<_>>());
|
||||
}
|
||||
//(Complex::new(re, im), None).into()
|
||||
let k: Tagged<()> = ((), None).into();
|
||||
k
|
||||
},
|
||||
);
|
||||
let tx_sink = NullSink::new(iq);
|
||||
|
||||
let host = cpal::default_host();
|
||||
let device = host
|
||||
.default_input_device()
|
||||
.expect("no output device available");
|
||||
let mut supported_configs_range = device
|
||||
.supported_input_configs()
|
||||
.expect("error while querying configs");
|
||||
let supported_config = supported_configs_range
|
||||
.next()
|
||||
.expect("no supported config?!")
|
||||
.with_sample_rate(SAMPLE_RATE as u32);
|
||||
let _stream = device
|
||||
.build_input_stream(
|
||||
&supported_config.into(),
|
||||
move |data: &[f32], _: &cpal::InputCallbackInfo| {
|
||||
for x in data
|
||||
{
|
||||
let _ = audio_tx.send(*x * 100.);
|
||||
}
|
||||
},
|
||||
move |_err| {},
|
||||
None, // None=blocking, Some(Duration)=timeout
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let graph = flowgraph![
|
||||
rx_source,
|
||||
downconverter,
|
||||
agc,
|
||||
matched_filter,
|
||||
splitter,
|
||||
proj_i,
|
||||
proj_q,
|
||||
elg_i,
|
||||
elg_q,
|
||||
merger,
|
||||
debug,
|
||||
tx_sink
|
||||
];
|
||||
graph.run(6);
|
||||
|
||||
let mut eye_i_history = HistoryBuf::new(vec![], 200);
|
||||
let mut eye_q_history = HistoryBuf::new(vec![], 200);
|
||||
let mut constellation = HistoryBuf::new(Complex::new(0., 0.), 500);
|
||||
let mut agc_error = HistoryBuf::new(0., 50_000);
|
||||
eframe::run_simple_native("Window", Default::default(), move |ctx, _frame| {
|
||||
for eye in eye_i_rx.try_iter().take(200)
|
||||
{
|
||||
eye_i_history.push(eye);
|
||||
}
|
||||
for eye in eye_q_rx.try_iter().take(200)
|
||||
{
|
||||
eye_q_history.push(eye);
|
||||
}
|
||||
for point in constellation_rx.try_iter().take(500)
|
||||
{
|
||||
constellation.push(point);
|
||||
}
|
||||
for x in agc_error_rx.try_iter().take(5000)
|
||||
{
|
||||
agc_error.push(x);
|
||||
}
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui_plot::Plot::new("plot")
|
||||
.data_aspect(1.)
|
||||
.show(ui, |plot_ui| {
|
||||
plot_ui.points(
|
||||
Points::new(
|
||||
"Constellation",
|
||||
constellation
|
||||
.as_slice()
|
||||
.iter()
|
||||
.map(|point| [point.re as f64, point.im as f64])
|
||||
.collect::<PlotPoints>(),
|
||||
)
|
||||
.color(Color32::YELLOW.gamma_multiply_u8(70)),
|
||||
);
|
||||
|
||||
plot_ui.line(
|
||||
Line::new(
|
||||
"AGC Error",
|
||||
agc_error
|
||||
.as_slice()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, point)| [map(i as f64, 0., 50_000., -2., 2.), *point as f64 + 2.])
|
||||
.collect::<PlotPoints>(),
|
||||
)
|
||||
.color(Color32::YELLOW.gamma_multiply_u8(70)),
|
||||
);
|
||||
|
||||
for (eye_i, eye_q) in eye_i_history
|
||||
.as_slice()
|
||||
.iter()
|
||||
.zip(eye_q_history.as_slice().iter())
|
||||
{
|
||||
plot_ui.line(
|
||||
Line::new(
|
||||
"In-Phase",
|
||||
eye_i
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| {
|
||||
[i as f64 / ((SAMPLE_PER_SYMBOL as f64 * 2.) / DECIMATION as f64) + 1., *x as f64]
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.color(Color32::RED),
|
||||
);
|
||||
|
||||
plot_ui.line(
|
||||
Line::new(
|
||||
"Quadrature",
|
||||
eye_q
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| {
|
||||
[*x as f64, i as f64 / ((SAMPLE_PER_SYMBOL as f64 * 2.) / DECIMATION as f64) - 2.]
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.color(Color32::GREEN),
|
||||
);
|
||||
|
||||
// plot_ui.points(
|
||||
// Points::new(
|
||||
// "Constellation",
|
||||
// eye_i
|
||||
// .iter()
|
||||
// .zip(eye_q.iter())
|
||||
// .skip(SAMPLE_PER_SYMBOL / DECIMATION)
|
||||
// .step_by(SAMPLE_PER_SYMBOL / DECIMATION)
|
||||
// .map(|(i, q)| [*i as f64, *q as f64])
|
||||
// .collect::<PlotPoints>(),
|
||||
// )
|
||||
// .color(Color32::GREEN)
|
||||
// .radius(1.5),
|
||||
// );
|
||||
}
|
||||
});
|
||||
});
|
||||
ctx.request_repaint();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn to_bits(n: u8) -> [bool; 8]
|
||||
{
|
||||
[
|
||||
(n & 1) == 1,
|
||||
(n >> 1) & 1 == 1,
|
||||
(n >> 2) & 1 == 1,
|
||||
(n >> 3) & 1 == 1,
|
||||
(n >> 4) & 1 == 1,
|
||||
(n >> 5) & 1 == 1,
|
||||
(n >> 6) & 1 == 1,
|
||||
(n >> 7) & 1 == 1,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn from_bits(n: [bool; 8]) -> u8
|
||||
{
|
||||
(n[0] as u8)
|
||||
| ((n[1] as u8) << 1)
|
||||
| ((n[2] as u8) << 2)
|
||||
| ((n[3] as u8) << 3)
|
||||
| ((n[4] as u8) << 4)
|
||||
| ((n[5] as u8) << 5)
|
||||
| ((n[6] as u8) << 6)
|
||||
| ((n[7] as u8) << 7)
|
||||
}
|
||||
8
examples/simple/Cargo.toml
Normal file
8
examples/simple/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "simple"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
oxydsp-flowgraph = {path = "../../oxydsp-flowgraph/"}
|
||||
oxydsp-dsp = {path = "../../oxydsp-dsp/"}
|
||||
31
examples/simple/src/main.rs
Normal file
31
examples/simple/src/main.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use oxydsp_dsp::blocks::utilities::adapters::NullSink;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::Scan;
|
||||
use oxydsp_dsp::blocks::utilities::channels::RxSource;
|
||||
use oxydsp_flowgraph::flowgraph;
|
||||
|
||||
fn main()
|
||||
{
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let (rx_source, numbers) = RxSource::new(rx);
|
||||
let (inspect, numbers) = Scan::new(numbers, 0, |state, x: usize| {
|
||||
if x.is_multiple_of(100)
|
||||
{
|
||||
println!("{}", x);
|
||||
}
|
||||
x
|
||||
});
|
||||
let null_sink = NullSink::new(numbers);
|
||||
let graph = flowgraph![rx_source, inspect, null_sink];
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut x = 0usize;
|
||||
loop
|
||||
{
|
||||
let _ = tx.send(x);
|
||||
x += 1;
|
||||
}
|
||||
});
|
||||
graph.run(1).join();
|
||||
}
|
||||
@ -7,3 +7,4 @@ edition = "2024"
|
||||
num = "0.4.3"
|
||||
oxydsp-flowgraph = {path = "../oxydsp-flowgraph/"}
|
||||
rustfft = "6.4.1"
|
||||
wide = "1.2.0"
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
pub mod filtering;
|
||||
pub mod iq;
|
||||
pub mod math;
|
||||
pub mod synthesis;
|
||||
pub mod ted;
|
||||
pub mod utilities;
|
||||
|
||||
2
oxydsp-dsp/src/blocks/filtering.rs
Normal file
2
oxydsp-dsp/src/blocks/filtering.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod fir;
|
||||
pub mod pulse_shaping;
|
||||
94
oxydsp-dsp/src/blocks/filtering/fir.rs
Normal file
94
oxydsp-dsp/src/blocks/filtering/fir.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use num::Zero;
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::block::BlockResult;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use oxydsp_flowgraph::tag::Tag;
|
||||
use std::iter::Sum;
|
||||
use std::ops::Add;
|
||||
use std::ops::Mul;
|
||||
|
||||
use crate::filtering::fir::Fir;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct FirFilter<F, T, O, const D: usize = 1>
|
||||
where
|
||||
T: Clone + Zero + 'static,
|
||||
F: Mul<T, Output = O> + Clone + 'static,
|
||||
O: Add<O, Output = O> + Sum + Clone + Zero + 'static,
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<O>,
|
||||
|
||||
filter: crate::filtering::fir::FirFilter<F, T, O>,
|
||||
}
|
||||
|
||||
impl<F, T, O> FirFilter<F, T, O, 1>
|
||||
where
|
||||
T: Clone + Zero + 'static,
|
||||
F: Mul<T, Output = O> + Clone + 'static,
|
||||
O: Add<O, Output = O> + Sum + Clone + Zero,
|
||||
{
|
||||
pub fn new(input: In<T>, impulse_response: Fir<F>) -> (Self, In<O>)
|
||||
{
|
||||
Self::new_decimating(input, impulse_response)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, O, const D: usize> FirFilter<F, T, O, D>
|
||||
where
|
||||
T: Clone + Zero + 'static,
|
||||
F: Mul<T, Output = O> + Clone + 'static,
|
||||
O: Add<O, Output = O> + Sum + Clone + Zero,
|
||||
{
|
||||
pub fn new_decimating(input: In<T>, impulse_response: Fir<F>) -> (Self, In<O>)
|
||||
{
|
||||
const { assert!(D != 0); };
|
||||
let (output, filtered) = oxydsp_flowgraph::io::stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
filter: crate::filtering::fir::FirFilter::new(impulse_response),
|
||||
},
|
||||
filtered,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T, O, const D: usize> Block for FirFilter<F, T, O, D>
|
||||
where
|
||||
T: Clone + Zero,
|
||||
F: Mul<T, Output = O> + Clone + 'static,
|
||||
O: Add<O, Output = O> + Sum + Clone + Zero,
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
if D > 1
|
||||
{
|
||||
let mut input_iter = self.input.iter();
|
||||
let mut output_pusher = self.output.write_push();
|
||||
while input_iter.len() >= D && output_pusher.len() > 0
|
||||
{
|
||||
// TODO: Maybe find a better way to do this.
|
||||
// I hope this is at least well optimized by the compilator
|
||||
let batch: [_; D] = std::array::from_fn(|_| input_iter.next().unwrap());
|
||||
let tag_batch: [_; D] = std::array::from_fn(|i| &batch[i].1);
|
||||
let data_batch: [_; D] = std::array::from_fn(|i| batch[i].0.clone());
|
||||
self.filter.insert_batch_ref(&data_batch);
|
||||
let _ = output_pusher
|
||||
.push((self.filter.filtered(), Tag::from_tag_opts(tag_batch.into_iter())).into());
|
||||
}
|
||||
}
|
||||
else if D == 1
|
||||
{
|
||||
self.output.push_iter(self.input.iter().map(|x| (self.filter.next(x.0), x.1).into()));
|
||||
}
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
77
oxydsp-dsp/src/blocks/filtering/pulse_shaping.rs
Normal file
77
oxydsp-dsp/src/blocks/filtering/pulse_shaping.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use std::iter::Sum;
|
||||
use std::ops::Add;
|
||||
|
||||
use num::Zero;
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
|
||||
use crate::filtering::fir::Fir;
|
||||
use crate::filtering::fir::FirFilter;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct PulseShaper<T: 'static + std::ops::Mul<Output = T> + std::iter::Sum + Add<T, Output = T> + Sum + Clone + Zero>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<T>,
|
||||
|
||||
symbol_length: usize,
|
||||
remaining: usize,
|
||||
pulse_shaper: FirFilter<T, T, T>,
|
||||
}
|
||||
|
||||
impl<T: 'static + std::ops::Mul<Output = T> + std::iter::Sum + std::clone::Clone + Zero> PulseShaper<T>
|
||||
{
|
||||
pub fn new(input: In<T>, pulse_shape: Fir<T>, symbol_length: usize) -> (Self, In<T>)
|
||||
{
|
||||
let (output, pulse_shaped) = oxydsp_flowgraph::io::stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
symbol_length,
|
||||
remaining: 0,
|
||||
pulse_shaper: FirFilter::new(pulse_shape),
|
||||
},
|
||||
pulse_shaped,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + std::ops::Mul<Output = T> + std::iter::Sum + std::clone::Clone + Zero> Block
|
||||
for PulseShaper<T>
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
let mut reader = self.input.iter();
|
||||
let mut writer = self.output.write_push();
|
||||
|
||||
for _ in 0..writer.len()
|
||||
{
|
||||
if self.remaining == 0
|
||||
{
|
||||
if let Some(input) = reader.next()
|
||||
{
|
||||
let (data, tag) = input.into();
|
||||
let _ = writer.push((self.pulse_shaper.next(data), tag).into());
|
||||
self.remaining = self.symbol_length - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return oxydsp_flowgraph::block::BlockResult::Ok;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
let _ = writer.push(self.pulse_shaper.next(T::zero()).into());
|
||||
self.remaining -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
oxydsp_flowgraph::block::BlockResult::Ok
|
||||
}
|
||||
}
|
||||
1
oxydsp-dsp/src/blocks/iq.rs
Normal file
1
oxydsp-dsp/src/blocks/iq.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod zero_if;
|
||||
74
oxydsp-dsp/src/blocks/iq/zero_if.rs
Normal file
74
oxydsp-dsp/src/blocks/iq/zero_if.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use num::Complex;
|
||||
use num::Float;
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use rustfft::FftNum;
|
||||
|
||||
use crate::filtering::fir::Fir;
|
||||
use crate::filtering::fir::FirFilter;
|
||||
use crate::synthesis::oscillator::Nco;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct ZeroIf<T: std::clone::Clone + num::Num + Float + From<f32> + 'static>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<Complex<T>>,
|
||||
|
||||
local_oscillator: Nco<T>,
|
||||
filter: FirFilter<Complex<T>, Complex<T>, Complex<T>>,
|
||||
}
|
||||
|
||||
impl<T> ZeroIf<T>
|
||||
where
|
||||
T: std::clone::Clone
|
||||
+ num::Num
|
||||
+ FftNum
|
||||
+ From<f32>
|
||||
+ 'static
|
||||
+ num::Float
|
||||
+ num::traits::FloatConst,
|
||||
{
|
||||
pub fn new(input: In<T>, lo: Nco<T>) -> (Self, In<Complex<T>>)
|
||||
{
|
||||
let (output, port) = oxydsp_flowgraph::io::stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
local_oscillator: lo,
|
||||
filter: FirFilter::new(Fir::lowpass(lo.frequency(), 100).normalized_len()),
|
||||
},
|
||||
port,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_fir(&mut self, fir: Fir<Complex<T>>)
|
||||
{
|
||||
self.filter = FirFilter::new(fir);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Block for ZeroIf<T>
|
||||
where
|
||||
T: std::clone::Clone + num::Num + Float + From<f32> + 'static + num::Float,
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
self.output.push_iter(self.input.iter().map(|input| {
|
||||
let (data, tag) = input.into();
|
||||
// Mix
|
||||
let lo_sample = self.local_oscillator.next().unwrap();
|
||||
let iq = Complex::new(data * lo_sample.re, data * lo_sample.im);
|
||||
|
||||
(self.filter.next(iq), tag).into()
|
||||
}));
|
||||
oxydsp_flowgraph::block::BlockResult::Ok
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,12 @@
|
||||
use std::ops::{Add, Mul};
|
||||
use std::ops::Add;
|
||||
use std::ops::Mul;
|
||||
|
||||
use oxydsp_flowgraph::{
|
||||
BlockIO,
|
||||
block::{Block, BlockResult},
|
||||
io::{In, Out, PopIterable},
|
||||
tag::TagMergable,
|
||||
};
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::block::BlockResult;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use oxydsp_flowgraph::tag::Tag;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct Adder<Ia, Ib, O>
|
||||
@ -53,9 +54,12 @@ where
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.output.push_iter(
|
||||
(&mut self.input_a, &mut self.input_b)
|
||||
.pop_iter()
|
||||
.map(|(a, b)| (a.0 + b.0, a.1.merge(&b.1))),
|
||||
self.input_a
|
||||
.iter()
|
||||
.zip(self.input_b.iter())
|
||||
.map(|(x, y)| {
|
||||
(x.0 + y.0, Tag::from_tag_opts([&x.1, &y.1].into_iter())).into()
|
||||
})
|
||||
);
|
||||
BlockResult::Ok
|
||||
}
|
||||
@ -107,9 +111,12 @@ where
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.output.push_iter(
|
||||
(&mut self.input_a, &mut self.input_b)
|
||||
.pop_iter()
|
||||
.map(|(a, b)| (a.0 * b.0, a.1.merge(&b.1))),
|
||||
self.input_a
|
||||
.iter()
|
||||
.zip(self.input_b.iter())
|
||||
.map(|(x, y)| {
|
||||
(x.0 * y.0, Tag::from_tag_opts([&x.1, &y.1].into_iter())).into()
|
||||
})
|
||||
);
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::block::BlockResult;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use oxydsp_flowgraph::io::PopIterable;
|
||||
use oxydsp_flowgraph::io::stream;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
@ -31,7 +30,8 @@ impl<T: Float + From<f32> + 'static> Block for OscillatorSource<T>
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
self.output.push_iter((&mut self.nco).map(|x| (x, None)));
|
||||
self.output
|
||||
.push_iter((&mut self.nco).map(|x| (x, None).into()));
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
@ -80,9 +80,9 @@ impl<T: Float + From<f32> + 'static> Block for Nco<T>
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
self.output
|
||||
.push_iter(&mut self.frequency.pop_iter().map(|f| {
|
||||
.push_iter(&mut self.frequency.iter().map(|f| {
|
||||
self.nco.set_frequency(f.0);
|
||||
(self.nco.next().unwrap(), f.1)
|
||||
(self.nco.next().unwrap(), f.1).into()
|
||||
}));
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
1
oxydsp-dsp/src/blocks/ted.rs
Normal file
1
oxydsp-dsp/src/blocks/ted.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod early_late;
|
||||
113
oxydsp-dsp/src/blocks/ted/early_late.rs
Normal file
113
oxydsp-dsp/src/blocks/ted/early_late.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use std::iter::Sum;
|
||||
|
||||
use num::Float;
|
||||
use num::NumCast;
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use oxydsp_flowgraph::tag::Tag;
|
||||
use oxydsp_flowgraph::tag::TagKey;
|
||||
|
||||
use crate::filtering::fir::Fir;
|
||||
use crate::filtering::fir::FirFilter;
|
||||
use crate::filtering::history_buf::HistoryBuf;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct EarlyLateGate<T: Float + Send + Sync + Sum + Clone + NumCast + 'static>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<T>,
|
||||
|
||||
symbol_length: usize,
|
||||
|
||||
// Window looking at symbol_length samples at a time
|
||||
window: HistoryBuf<T>,
|
||||
|
||||
// The current location of the window, in relation to the last sample
|
||||
window_location: usize,
|
||||
|
||||
window_center: usize,
|
||||
|
||||
// The next window location, in relation to the last sample such that the window is centered on
|
||||
// a symbol center (hopefully)
|
||||
next_sample: f32,
|
||||
loop_filter: FirFilter<T, T, T>,
|
||||
|
||||
symbol_tag_key: TagKey<T>,
|
||||
}
|
||||
|
||||
impl<T> EarlyLateGate<T>
|
||||
where
|
||||
T: Float + Sum + Clone + 'static + Send + Sync + NumCast + Default,
|
||||
{
|
||||
pub fn new(
|
||||
input: In<T>,
|
||||
loop_filter: Fir<T>,
|
||||
symbol_length: usize,
|
||||
symbol_tag_key: TagKey<T>,
|
||||
) -> (Self, In<T>)
|
||||
{
|
||||
let (output, samples) = oxydsp_flowgraph::io::stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
window: HistoryBuf::new(Default::default(), symbol_length),
|
||||
symbol_length,
|
||||
window_location: 0,
|
||||
window_center: symbol_length / 2,
|
||||
next_sample: symbol_length as f32, // We assume that the first symbol is 1.5 windows into
|
||||
// the stream
|
||||
loop_filter: FirFilter::new(loop_filter),
|
||||
symbol_tag_key,
|
||||
},
|
||||
samples,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'view, T> Block for EarlyLateGate<T>
|
||||
where
|
||||
T: Float + Sum + Clone + 'static + Send + Sync + NumCast,
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
self.output.push_iter(self.input.iter().map(|input| {
|
||||
// Bring new sample in
|
||||
self.window.push(input.0);
|
||||
self.window_location += 1;
|
||||
|
||||
let sample = self.window.as_slice()[self.window_center];
|
||||
let mut tag = None;
|
||||
if self.window_location >= self.next_sample as usize
|
||||
{
|
||||
let early_index = self.window_center - (0.25 * self.symbol_length as f32) as usize;
|
||||
let late_index = self.window_center + (0.25 * self.symbol_length as f32) as usize;
|
||||
|
||||
let early_sample = self.window.as_slice()[early_index];
|
||||
let late_sample = self.window.as_slice()[late_index];
|
||||
|
||||
let error = (late_sample - early_sample) * sample;
|
||||
let correction = self.loop_filter.next(error);
|
||||
|
||||
// Figure out next sample location
|
||||
self.next_sample +=
|
||||
(self.symbol_length as f32 + correction.to_f32().unwrap()).max(0.);
|
||||
|
||||
// Turn everything back relative to current sample
|
||||
self.next_sample -= self.window_location as f32;
|
||||
self.window_location = 0;
|
||||
|
||||
tag = Some(Tag::with_entry(self.symbol_tag_key.clone(), error));
|
||||
}
|
||||
|
||||
(sample, tag).into()
|
||||
}));
|
||||
|
||||
oxydsp_flowgraph::block::BlockResult::Ok
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
pub mod adapters;
|
||||
pub mod channels;
|
||||
pub mod iter;
|
||||
pub mod squelch;
|
||||
pub mod graph_control;
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
use oxydsp_flowgraph::{
|
||||
BlockIO,
|
||||
block::{Block, BlockResult},
|
||||
io::{In, Out, PopIterable, stream},
|
||||
tag::Tag,
|
||||
};
|
||||
use std::iter::FusedIterator;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::block::BlockResult;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use oxydsp_flowgraph::io::stream;
|
||||
use oxydsp_flowgraph::tag::Tag;
|
||||
use oxydsp_flowgraph::tag::TagMergable;
|
||||
use oxydsp_flowgraph::tag::Tagged;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct Map<I: 'static, O: 'static, F>
|
||||
@ -19,7 +25,7 @@ pub struct Map<I: 'static, O: 'static, F>
|
||||
|
||||
impl<I: 'static, O: 'static, F> Map<I, O, F>
|
||||
where
|
||||
F: Fn(I) -> O,
|
||||
F: FnMut(I) -> O,
|
||||
{
|
||||
pub fn new(input: In<I>, map: F) -> (Self, In<O>)
|
||||
{
|
||||
@ -30,12 +36,220 @@ where
|
||||
|
||||
impl<I: 'static, O: 'static, F> Block for Map<I, O, F>
|
||||
where
|
||||
F: Fn(I) -> O,
|
||||
F: FnMut(I) -> O,
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
self.output
|
||||
.push_iter(self.input.pop_iter().map(|x| ((&self.map)(x.0), x.1)));
|
||||
.push_iter(self.input.iter().map(|x| ((&mut self.map)(x.0), x.1).into()));
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct MapResult<I: 'static, O: 'static, F>
|
||||
{
|
||||
#[input]
|
||||
input: In<I>,
|
||||
|
||||
#[output]
|
||||
output: Out<O>,
|
||||
|
||||
map: F,
|
||||
}
|
||||
|
||||
impl<I: 'static, O: 'static, F> MapResult<I, O, F>
|
||||
where
|
||||
F: Fn(I) -> (O, BlockResult),
|
||||
{
|
||||
pub fn new(input: In<I>, map: F) -> (Self, In<O>)
|
||||
{
|
||||
let (output, mapped) = stream();
|
||||
(Self { input, output, map }, mapped)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: 'static, O: 'static, F> Block for MapResult<I, O, F>
|
||||
where
|
||||
F: Fn(I) -> (O, BlockResult),
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
let mut writer = self.output.write_push();
|
||||
let mut reader = self.input.iter();
|
||||
|
||||
for _ in 0..(writer.len().min(reader.len()))
|
||||
{
|
||||
let (input, tag_opt) = reader.next().unwrap().into();
|
||||
let (output, result) = (self.map)(input);
|
||||
let _ = writer.push((output, tag_opt).into());
|
||||
match result
|
||||
{
|
||||
BlockResult::Terminated | BlockResult::Exit =>
|
||||
{
|
||||
return result;
|
||||
}
|
||||
BlockResult::Ok =>
|
||||
{}
|
||||
}
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct MapResultTagged<I: 'static, O: 'static, F>
|
||||
{
|
||||
#[input]
|
||||
input: In<I>,
|
||||
|
||||
#[output]
|
||||
output: Out<O>,
|
||||
|
||||
map: F,
|
||||
}
|
||||
|
||||
impl<I: 'static, O: 'static, F> MapResultTagged<I, O, F>
|
||||
where
|
||||
F: Fn(Tagged<I>) -> (Tagged<O>, BlockResult),
|
||||
{
|
||||
pub fn new(input: In<I>, map: F) -> (Self, In<O>)
|
||||
{
|
||||
let (output, mapped) = stream();
|
||||
(Self { input, output, map }, mapped)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: 'static, O: 'static, F> Block for MapResultTagged<I, O, F>
|
||||
where
|
||||
F: Fn(Tagged<I>) -> (Tagged<O>, BlockResult),
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
let mut writer = self.output.write_push();
|
||||
let mut reader = self.input.iter();
|
||||
|
||||
for _ in 0..(writer.len().min(reader.len()))
|
||||
{
|
||||
let (input, tag_opt) = reader.next().unwrap().into();
|
||||
let (tagged_out, result) = (self.map)((input, tag_opt.clone()).into());
|
||||
let (output, tag_out) = tagged_out.into();
|
||||
|
||||
let _ = writer.push((output, tag_opt.merge(&tag_out)).into());
|
||||
match result
|
||||
{
|
||||
BlockResult::Terminated | BlockResult::Exit =>
|
||||
{
|
||||
return result;
|
||||
}
|
||||
BlockResult::Ok =>
|
||||
{}
|
||||
}
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct Scan<I: 'static, O: 'static, S, F>
|
||||
where
|
||||
F: FnMut(&mut S, I) -> O,
|
||||
{
|
||||
#[input]
|
||||
input: In<I>,
|
||||
|
||||
#[output]
|
||||
output: Out<O>,
|
||||
|
||||
state: S,
|
||||
|
||||
map: F,
|
||||
}
|
||||
|
||||
impl<I: 'static, O: 'static, S, F> Scan<I, O, S, F>
|
||||
where
|
||||
F: FnMut(&mut S, I) -> O,
|
||||
{
|
||||
pub fn new(input: In<I>, initial_state: S, map: F) -> (Self, In<O>)
|
||||
{
|
||||
let (output, mapped) = stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
state: initial_state,
|
||||
map,
|
||||
},
|
||||
mapped,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, S, F> Block for Scan<I, O, S, F>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
F: FnMut(&mut S, I) -> O,
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.output.push_iter(
|
||||
self.input
|
||||
.iter()
|
||||
.map(|x| ((self.map)(&mut self.state, x.0), x.1).into()),
|
||||
);
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct ScanTagged<I: 'static, O: 'static, S, F>
|
||||
where
|
||||
F: Fn(&mut S, Tagged<I>) -> Tagged<O>,
|
||||
{
|
||||
#[input]
|
||||
input: In<I>,
|
||||
|
||||
#[output]
|
||||
output: Out<O>,
|
||||
|
||||
state: S,
|
||||
|
||||
map: F,
|
||||
}
|
||||
|
||||
impl<I: 'static, O: 'static, S, F> ScanTagged<I, O, S, F>
|
||||
where
|
||||
F: Fn(&mut S, Tagged<I>) -> Tagged<O>,
|
||||
{
|
||||
pub fn new(input: In<I>, initial_state: S, map: F) -> (Self, In<O>)
|
||||
{
|
||||
let (output, mapped) = stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
state: initial_state,
|
||||
map,
|
||||
},
|
||||
mapped,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, S, F> Block for ScanTagged<I, O, S, F>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
F: Fn(&mut S, Tagged<I>) -> Tagged<O>,
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.output.push_iter(self.input.iter().map(|tagged| {
|
||||
let cloned_tag = tagged.1.clone();
|
||||
let (output, tag) = (self.map)(&mut self.state, (tagged.0, cloned_tag).into()).into();
|
||||
(output, Tag::from_tag_opts([&tagged.1, &tag].into_iter())).into()
|
||||
}));
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
@ -76,30 +290,32 @@ impl<T: Clone + 'static> Block for Repeat<T>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
let writer = self.output.write();
|
||||
let reader = self.input.read();
|
||||
let mut writer = self.output.write_push();
|
||||
let mut reader = self.input.iter();
|
||||
let len = writer.len().min(reader.len() / self.repetitions + 1);
|
||||
|
||||
for _ in 0..len
|
||||
{
|
||||
if self.remaining == 0 || self.current.is_none()
|
||||
{
|
||||
self.current = Some(reader.pop_tagged().unwrap());
|
||||
self.remaining = self.repetitions;
|
||||
if let Some(x) = reader.next()
|
||||
{
|
||||
self.current = Some(x.into());
|
||||
self.remaining = self.repetitions;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BlockResult::Ok;
|
||||
}
|
||||
}
|
||||
|
||||
writer
|
||||
.push(self.current.clone().unwrap())
|
||||
.push(self.current.clone().unwrap().into())
|
||||
.unwrap_or_else(|_| panic!());
|
||||
|
||||
match &mut self.current
|
||||
if let Some((_, tag)) = &mut self.current
|
||||
{
|
||||
Some((_, tag)) =>
|
||||
{
|
||||
*tag = None;
|
||||
}
|
||||
None =>
|
||||
{}
|
||||
*tag = None;
|
||||
}
|
||||
|
||||
self.remaining -= 1;
|
||||
@ -108,3 +324,295 @@ impl<T: Clone + 'static> Block for Repeat<T>
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct NullSink<T: 'static>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> NullSink<T>
|
||||
{
|
||||
pub fn new(input: In<T>) -> Self
|
||||
{
|
||||
Self { input }
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: 'static> Block for NullSink<I>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.input.iter().for_each(|_| ());
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct Tee<T: 'static + Clone>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output_a: Out<T>,
|
||||
|
||||
#[output]
|
||||
output_b: Out<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone> Tee<T>
|
||||
{
|
||||
pub fn new(input: In<T>) -> (Self, In<T>, In<T>)
|
||||
{
|
||||
let (output_a, port_a) = stream();
|
||||
let (output_b, port_b) = stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output_a,
|
||||
output_b,
|
||||
},
|
||||
port_a,
|
||||
port_b,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone> Block for Tee<T>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
let mut writer_a = self.output_a.write_push();
|
||||
let mut writer_b = self.output_b.write_push();
|
||||
for x in self.input.iter().take(writer_a.len().min(writer_b.len()))
|
||||
{
|
||||
let _ = writer_a.push(x.clone());
|
||||
let _ = writer_b.push(x);
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct FlatMap<I, O, F>
|
||||
where
|
||||
I: 'static,
|
||||
O: IntoIterator + 'static,
|
||||
O::IntoIter: FusedIterator,
|
||||
F: Fn(I) -> O,
|
||||
{
|
||||
#[input]
|
||||
input: In<I>,
|
||||
|
||||
#[output]
|
||||
output: Out<O::Item>,
|
||||
|
||||
current_iter: Option<O::IntoIter>,
|
||||
map: F,
|
||||
}
|
||||
|
||||
impl<I, O, F> FlatMap<I, O, F>
|
||||
where
|
||||
I: 'static,
|
||||
O: IntoIterator + 'static,
|
||||
O::IntoIter: FusedIterator,
|
||||
F: Fn(I) -> O,
|
||||
{
|
||||
pub fn new(input: In<I>, map: F) -> (Self, In<O::Item>)
|
||||
{
|
||||
let (output, port) = oxydsp_flowgraph::io::stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
current_iter: None,
|
||||
map,
|
||||
},
|
||||
port,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, F> Block for FlatMap<I, O, F>
|
||||
where
|
||||
I: 'static,
|
||||
O: IntoIterator + 'static,
|
||||
O::IntoIter: FusedIterator,
|
||||
F: Fn(I) -> O,
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
let mut writer = self.output.write_push();
|
||||
let mut reader = self.input.iter();
|
||||
|
||||
let max_write = writer.len();
|
||||
let mut written = 0;
|
||||
|
||||
while written < max_write
|
||||
{
|
||||
if let Some(current_iter) = self.current_iter.as_mut()
|
||||
{
|
||||
if let Some(next_elt) = current_iter.next()
|
||||
{
|
||||
let _ = writer.push((next_elt, None).into());
|
||||
written += 1;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Iterator empty
|
||||
self.current_iter = None;
|
||||
}
|
||||
}
|
||||
|
||||
if self.current_iter.is_none()
|
||||
{
|
||||
// Get input
|
||||
if let Some(input) = reader.next()
|
||||
{
|
||||
let mut new_iter = (self.map)(input.0).into_iter();
|
||||
if let Some(first_elt) = new_iter.next()
|
||||
{
|
||||
self.current_iter = Some(new_iter);
|
||||
let _ = writer.push((first_elt, input.1).into());
|
||||
written += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Iterator empty
|
||||
self.current_iter = None;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cannot continue
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct Splitter<T: 'static + Clone, const N: usize>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
outputs: [Out<T>; N],
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone, const N: usize> Splitter<T, N>
|
||||
{
|
||||
pub fn new(input: In<T>) -> (Self, [In<T>; N])
|
||||
{
|
||||
const { assert!(N > 0) }
|
||||
let (outs, ins) = oxydsp_flowgraph::io::streams();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
outputs: outs,
|
||||
},
|
||||
ins,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static + Clone, const N: usize> Block for Splitter<T, N>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
let mut input_iter = self.input.iter();
|
||||
let mut outputs = self
|
||||
.outputs
|
||||
.iter_mut()
|
||||
.map(|x| x.write_push())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let length = input_iter
|
||||
.len()
|
||||
.min(outputs.iter().map(|x| x.len()).min().unwrap());
|
||||
|
||||
for _ in 0..length
|
||||
{
|
||||
let pulled = input_iter.next().unwrap();
|
||||
outputs.iter_mut().for_each(|x| {
|
||||
let _ = x.push(pulled.clone());
|
||||
});
|
||||
}
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct Merger<T: 'static, const N: usize>
|
||||
{
|
||||
#[input]
|
||||
inputs: [In<T>; N],
|
||||
|
||||
#[output]
|
||||
output: Out<[T; N]>,
|
||||
}
|
||||
|
||||
impl<T: 'static, const N: usize> Merger<T, N>
|
||||
{
|
||||
pub fn new(inputs: [In<T>; N]) -> (Self, In<[T; N]>)
|
||||
{
|
||||
const { assert!(N > 0) }
|
||||
let (out, inp) = oxydsp_flowgraph::io::stream();
|
||||
|
||||
(
|
||||
Self {
|
||||
inputs,
|
||||
output: out,
|
||||
},
|
||||
inp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sized + 'static, const N: usize> Block for Merger<T, N>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
let mut inputs = self.inputs.iter_mut().map(|x| x.iter()).collect::<Vec<_>>();
|
||||
let mut output = self.output.write_push();
|
||||
|
||||
let len = inputs
|
||||
.len()
|
||||
.min(inputs.iter().map(|x| x.len()).min().unwrap());
|
||||
|
||||
let mut datas: [_; N] = std::array::from_fn(|_| MaybeUninit::uninit());
|
||||
let mut tags: [_; N] = std::array::from_fn(|_| MaybeUninit::uninit());
|
||||
for _ in 0..len
|
||||
{
|
||||
for (i, (data, tag)) in inputs
|
||||
.iter_mut()
|
||||
.map(|x| x.next().unwrap().into())
|
||||
.enumerate()
|
||||
{
|
||||
datas[i] = MaybeUninit::new(data);
|
||||
tags[i] = MaybeUninit::new(tag);
|
||||
}
|
||||
|
||||
let ok_datas: [_; N] = unsafe {
|
||||
std::array::from_fn(|i| std::mem::replace(&mut datas[i], MaybeUninit::uninit()).assume_init())
|
||||
};
|
||||
let ok_tags = unsafe {
|
||||
std::mem::transmute::<&[MaybeUninit<Option<Tag>>; N], &[Option<Tag>; N]>(&tags)
|
||||
};
|
||||
|
||||
let tag = Tag::from_tag_opts(ok_tags.into_iter());
|
||||
output.push((ok_datas, tag).into());
|
||||
}
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
use std::sync::mpsc::{Receiver, Sender, SyncSender};
|
||||
use std::fmt::Debug;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::mpsc::SyncSender;
|
||||
|
||||
use oxydsp_flowgraph::{
|
||||
BlockIO,
|
||||
block::{Block, BlockResult},
|
||||
io::{In, Out, PopIterable, stream},
|
||||
};
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::block::BlockResult;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use oxydsp_flowgraph::io::stream;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct RxSource<Rx, I: 'static>
|
||||
@ -41,18 +45,13 @@ impl<Tx, I: 'static> TxSink<Tx, I>
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: 'static> Block for RxSource<Receiver<I>, I>
|
||||
impl<I: 'static + Debug> Block for RxSource<Receiver<I>, I>
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
if self.output.push_iter(self.input.iter().map(|x| (x, None)))
|
||||
{
|
||||
BlockResult::Ok
|
||||
}
|
||||
else
|
||||
{
|
||||
BlockResult::Terminated
|
||||
}
|
||||
self.output
|
||||
.push_iter(self.input.try_iter().map(|x| (x, None).into()));
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +61,7 @@ impl<I: 'static> Block for TxSink<Sender<I>, I>
|
||||
{
|
||||
if self
|
||||
.input
|
||||
.pop_iter()
|
||||
.iter()
|
||||
.map(|x| self.output.send(x.0))
|
||||
.any(|res| res.is_err())
|
||||
{
|
||||
@ -81,7 +80,7 @@ impl<I: 'static> Block for TxSink<SyncSender<I>, I>
|
||||
{
|
||||
if self
|
||||
.input
|
||||
.pop_iter()
|
||||
.iter()
|
||||
.map(|x| self.output.send(x.0))
|
||||
.any(|res| res.is_err())
|
||||
{
|
||||
|
||||
53
oxydsp-dsp/src/blocks/utilities/graph_control.rs
Normal file
53
oxydsp-dsp/src/blocks/utilities/graph_control.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use oxydsp_flowgraph::{BlockIO, block::Block, io::{In, Out}, tag::TagKey};
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct GraphKiller<T: 'static, K: Send>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<T>,
|
||||
|
||||
kill_tag: TagKey<K>
|
||||
}
|
||||
|
||||
impl<T: 'static, K: Send + Send + 'static> GraphKiller<T, K>
|
||||
{
|
||||
pub fn new(input: In<T>, kill_on: TagKey<K>) -> (Self, In<T>)
|
||||
{
|
||||
let (output, port) = oxydsp_flowgraph::io::stream();
|
||||
(
|
||||
GraphKiller
|
||||
{
|
||||
input,
|
||||
output,
|
||||
kill_tag: kill_on,
|
||||
},
|
||||
port
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, K: Send + Sync + 'static> Block for GraphKiller<T, K>
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
let mut reader = self.input.iter();
|
||||
let mut writer = self.output.write_push();
|
||||
for _ in 0..reader.len().min(writer.len())
|
||||
{
|
||||
let (data, tag) = reader.next().unwrap().into();
|
||||
|
||||
if tag.as_ref().is_some_and(|t| t.retrieve(&self.kill_tag).is_some())
|
||||
{
|
||||
return oxydsp_flowgraph::block::BlockResult::Exit;
|
||||
}
|
||||
|
||||
let _ = writer.push((data, tag).into());
|
||||
}
|
||||
|
||||
|
||||
oxydsp_flowgraph::block::BlockResult::Ok
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,21 @@
|
||||
use oxydsp_flowgraph::{
|
||||
BlockIO,
|
||||
block::{Block, BlockResult},
|
||||
io::{In, Out, stream},
|
||||
};
|
||||
use std::iter::Peekable;
|
||||
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::block::BlockResult;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
use oxydsp_flowgraph::io::stream;
|
||||
use oxydsp_flowgraph::tag::Tag;
|
||||
use oxydsp_flowgraph::tag::TagKey;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct IterSource<I: Iterator>
|
||||
where
|
||||
I::Item: 'static,
|
||||
{
|
||||
iter: I,
|
||||
iter: Peekable<I>,
|
||||
finished_tag: Option<TagKey<()>>,
|
||||
|
||||
#[output]
|
||||
output: Out<I::Item>,
|
||||
@ -23,7 +29,19 @@ where
|
||||
pub fn new(iter: I) -> (Self, In<I::Item>)
|
||||
{
|
||||
let (output, items) = stream();
|
||||
(Self { iter, output }, items)
|
||||
(
|
||||
Self {
|
||||
iter: iter.peekable(),
|
||||
finished_tag: None,
|
||||
output,
|
||||
},
|
||||
items,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn tag_last_with(&mut self, finished_tag: TagKey<()>)
|
||||
{
|
||||
self.finished_tag = Some(finished_tag);
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,13 +52,21 @@ where
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
if self.output.push_iter((&mut self.iter).map(|x| (x, None)))
|
||||
let mut writer = self.output.write_push();
|
||||
|
||||
for _ in 0..writer.len()
|
||||
{
|
||||
BlockResult::Ok
|
||||
}
|
||||
else
|
||||
{
|
||||
BlockResult::Terminated
|
||||
if let Some(element) = self.iter.next()
|
||||
{
|
||||
let mut tag = None;
|
||||
if let Some(tag_key) = &self.finished_tag
|
||||
&& self.iter.peek().is_none()
|
||||
{
|
||||
tag = Some(Tag::with_entry(tag_key.clone(), ()));
|
||||
}
|
||||
let _ = writer.push((element, tag).into());
|
||||
}
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
82
oxydsp-dsp/src/blocks/utilities/squelch.rs
Normal file
82
oxydsp-dsp/src/blocks/utilities/squelch.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::iter::Sum;
|
||||
|
||||
use num::Float;
|
||||
use num::FromPrimitive;
|
||||
use num::One;
|
||||
use num::Zero;
|
||||
use num::complex::ComplexFloat;
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::Block;
|
||||
use oxydsp_flowgraph::block::BlockResult;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
use oxydsp_flowgraph::io::Out;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct Squelch<T>
|
||||
where
|
||||
T: ComplexFloat + 'static,
|
||||
T::Real: Float + One + Zero + FromPrimitive + Sum + Clone,
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
|
||||
#[output]
|
||||
output: Out<T>,
|
||||
|
||||
trigger_level: T::Real,
|
||||
|
||||
history: VecDeque<T::Real>,
|
||||
sum: T::Real,
|
||||
divider: T::Real,
|
||||
}
|
||||
|
||||
impl<T> Squelch<T>
|
||||
where
|
||||
T: ComplexFloat + 'static,
|
||||
T::Real: Float + Sum + Clone + FromPrimitive,
|
||||
{
|
||||
pub fn new(input: In<T>, trigger_level: T::Real, mean_length: usize) -> (Self, In<T>)
|
||||
{
|
||||
let (output, port) = oxydsp_flowgraph::io::stream();
|
||||
(
|
||||
Self {
|
||||
input,
|
||||
output,
|
||||
trigger_level,
|
||||
history: VecDeque::from(vec![T::Real::zero(); mean_length]),
|
||||
sum: T::Real::zero(),
|
||||
divider: T::Real::from_usize(mean_length).unwrap(),
|
||||
},
|
||||
port,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Block for Squelch<T>
|
||||
where
|
||||
T: ComplexFloat + 'static,
|
||||
T::Real: Float + Sum + Clone + FromPrimitive,
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
let mut writer = self.output.write_push();
|
||||
for x in self.input.iter().take(writer.len())
|
||||
{
|
||||
let (element, tag) = x.into();
|
||||
|
||||
let oldest = self.history.pop_front().unwrap();
|
||||
let newest = element.abs();
|
||||
self.history.push_back(newest);
|
||||
|
||||
self.sum = self.sum - oldest;
|
||||
self.sum = self.sum + newest;
|
||||
|
||||
if (self.sum / self.divider) > self.trigger_level
|
||||
{
|
||||
let _ = writer.push((element, tag).into());
|
||||
}
|
||||
}
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
2
oxydsp-dsp/src/filtering.rs
Normal file
2
oxydsp-dsp/src/filtering.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod fir;
|
||||
pub mod history_buf;
|
||||
377
oxydsp-dsp/src/filtering/fir.rs
Normal file
377
oxydsp-dsp/src/filtering/fir.rs
Normal file
@ -0,0 +1,377 @@
|
||||
use num::Complex;
|
||||
use num::Float;
|
||||
use num::FromPrimitive;
|
||||
use num::Num;
|
||||
use num::One;
|
||||
use num::Zero;
|
||||
use num::complex::ComplexFloat;
|
||||
use rustfft::FftNum;
|
||||
use rustfft::FftPlanner;
|
||||
use std::f64::consts::PI;
|
||||
use std::iter::Sum;
|
||||
use std::ops::Add;
|
||||
use std::ops::Div;
|
||||
use std::ops::Mul;
|
||||
|
||||
use crate::filtering::history_buf::HistoryBuf;
|
||||
use crate::map;
|
||||
use crate::units::DigitalFrequency;
|
||||
|
||||
/// Represents a finite impulse response as a vector
|
||||
/// of values in time.
|
||||
///
|
||||
/// Convention
|
||||
/// indices : 0 ----------------- fir.0.len - 1
|
||||
/// time : ---------------->
|
||||
///
|
||||
/// For a reverb ir for example the clap would be at index 0
|
||||
/// and the reverb tail towards the end of the vector.
|
||||
#[derive(Clone)]
|
||||
pub struct Fir<T>(pub Vec<T>);
|
||||
|
||||
impl<T> Fir<T>
|
||||
where
|
||||
T: Num + Clone + Sum,
|
||||
{
|
||||
pub fn convoluted_with(&self, other: &Fir<T>) -> Fir<T>
|
||||
{
|
||||
// Perform convolution
|
||||
let mut new_fir = vec![];
|
||||
let mut filter = FirFilter::<T, T, T>::new(self.clone());
|
||||
|
||||
for x in self.0.iter().rev()
|
||||
{
|
||||
new_fir.push(filter.next(x.clone()));
|
||||
}
|
||||
|
||||
for _ in 0..other.0.len()
|
||||
{
|
||||
new_fir.push(filter.next(T::zero()));
|
||||
}
|
||||
|
||||
Fir(new_fir)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Fir<Complex<T>>
|
||||
where
|
||||
T: FftNum + Float + Clone,
|
||||
{
|
||||
/// Synthesizes an impulse response from a transfer function using an inverse fourrier
|
||||
/// transform.
|
||||
///
|
||||
/// The input units are thus :
|
||||
/// Transfer function :
|
||||
/// start center end
|
||||
/// [ ]
|
||||
/// frequency :
|
||||
/// 0 pi 2*pi
|
||||
/// = pi, -pi 0
|
||||
/// nyquist's frequency
|
||||
/// (As the frequencies are periodic)
|
||||
///
|
||||
pub fn from_transfer_function(tf: impl AsRef<[Complex<T>]>) -> Fir<Complex<T>>
|
||||
{
|
||||
let mut planner = FftPlanner::new();
|
||||
let tf_len = tf.as_ref().len();
|
||||
let ifft = planner.plan_fft_inverse(tf.as_ref().len());
|
||||
|
||||
let mut fir = tf.as_ref().to_vec();
|
||||
ifft.process(fir.as_mut_slice());
|
||||
|
||||
let mut shifted_fir = vec![];
|
||||
for i in 0..tf_len
|
||||
{
|
||||
let k = (tf_len - (tf_len / 2) + i) % tf_len;
|
||||
shifted_fir.push(fir[k]);
|
||||
}
|
||||
|
||||
Fir(shifted_fir)
|
||||
}
|
||||
|
||||
/// Creates a low pass filter with the following ideal transfer function using the ifft method:
|
||||
///
|
||||
/// ________________ ________________
|
||||
/// |____________________|
|
||||
/// 0 cuttoff -cuttoff 0
|
||||
///
|
||||
pub fn lowpass(cutoff: DigitalFrequency, length: usize) -> Fir<Complex<T>>
|
||||
{
|
||||
let mut tf = vec![Complex::<T>::zero(); length];
|
||||
|
||||
let cutoff_bin = map(cutoff.as_rad(), 0., 2. * PI, 0., length as f64).floor() as usize;
|
||||
for i in 0..cutoff_bin
|
||||
{
|
||||
tf[i] = Complex::<T>::one();
|
||||
tf[length - i - 1] = Complex::<T>::one();
|
||||
}
|
||||
|
||||
Self::from_transfer_function(tf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Fir<T>
|
||||
where
|
||||
T: ComplexFloat + Div<T::Real, Output = T> + Copy + Sum,
|
||||
T::Real: Float,
|
||||
{
|
||||
/// Returns the same impulse response
|
||||
/// normalized by the length of the sum of the vectors.
|
||||
pub fn normalized(mut self) -> Self
|
||||
{
|
||||
let sum: T = self.0.iter().copied().sum();
|
||||
let len = Float::sqrt(sum.im() * sum.im() + sum.re() * sum.re());
|
||||
|
||||
self.0.iter_mut().for_each(|x| *x = *x / len);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Fir<T>
|
||||
where
|
||||
T: ComplexFloat + Div<T::Real, Output = T>,
|
||||
T::Real: Float + FromPrimitive,
|
||||
{
|
||||
/// Returns the same impulse response
|
||||
/// normalized by the length of the impulse response.
|
||||
pub fn normalized_len(mut self) -> Self
|
||||
{
|
||||
let len = T::Real::from_usize(self.0.len()).unwrap();
|
||||
self.0.iter_mut().for_each(|x| *x = *x / len);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Fir<T>
|
||||
where
|
||||
T: ComplexFloat + Div<T::Real, Output = T> + Copy + Add<T, Output = T>,
|
||||
T::Real: Float,
|
||||
{
|
||||
/// Returns the same impulse response
|
||||
/// normalized by the energy or the sum of the squares of the magnitues
|
||||
/// of the impulse response
|
||||
pub fn normalized_sqr(mut self) -> Self
|
||||
{
|
||||
let sum = self
|
||||
.0
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|x| x.abs() * x.abs())
|
||||
.reduce(|x, y| x + y)
|
||||
.unwrap();
|
||||
|
||||
self.0.iter_mut().for_each(|x| *x = *x / sum);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Fir<T>
|
||||
where
|
||||
T: ComplexFloat + FromPrimitive,
|
||||
{
|
||||
/// Creates a square unit impulse response :
|
||||
/// a vector of length `length` filled with ones
|
||||
pub fn square(length: usize) -> Self
|
||||
{
|
||||
Self((0..length).map(|_| T::one()).collect())
|
||||
}
|
||||
|
||||
/// Creates a simple proportional integral (PI) loop impulse response :
|
||||
///
|
||||
/// FIR:
|
||||
/// ```text
|
||||
/// _ ................................... Kp (`proportional_gain`)
|
||||
/// |
|
||||
/// |
|
||||
/// |
|
||||
/// |____________________________ ...... Ki (`integral_gain`)
|
||||
/// |
|
||||
///
|
||||
/// 0 ------------------------- `length`
|
||||
/// ```
|
||||
///
|
||||
pub fn proportional_integral(length: usize, proportional_gain: T, integral_gain: T) -> Self
|
||||
{
|
||||
Self(
|
||||
(0..length)
|
||||
.map(|i| {
|
||||
if i == 0
|
||||
{
|
||||
proportional_gain + integral_gain
|
||||
}
|
||||
else
|
||||
{
|
||||
integral_gain
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a root raised cosine (RRC) FIR of length `length`
|
||||
/// with the given roll off factor.
|
||||
///
|
||||
/// The corresponding RC (convolution of this filter with itself)
|
||||
/// has its zero crossing every `symbol_length` samples (except at 0).
|
||||
pub fn root_raised_cosine(length: usize, roll_off: f64, symbol_length: usize) -> Self
|
||||
{
|
||||
Self(
|
||||
(0..length)
|
||||
.map(|i| {
|
||||
let t = map(
|
||||
i as f64,
|
||||
0.,
|
||||
length as f64,
|
||||
-(length as f64) * 0.5,
|
||||
length as f64 * 0.5,
|
||||
) / symbol_length as f64;
|
||||
T::from_f64(root_raised_cosine(t, roll_off, 1.)).unwrap()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a gaussion fir of length `length`
|
||||
/// The maximum amplitude of the fir is 1.
|
||||
/// The gaussian curve is centered in the fir.
|
||||
/// The parameter `standard_deviations` specifies how many multiples of the
|
||||
/// standard deviations is on the left and right of the center before the fir cuts it off.
|
||||
pub fn gaussian(length: usize, standard_deviations: f32) -> Self
|
||||
{
|
||||
Self(
|
||||
(0..length)
|
||||
.map(|x| {
|
||||
let t = map(
|
||||
x as f64,
|
||||
0.,
|
||||
length as f64,
|
||||
-standard_deviations as f64,
|
||||
standard_deviations as f64,
|
||||
);
|
||||
|
||||
// Gaussian with sd=1
|
||||
let sq = t / 2.;
|
||||
T::from_f64(-sq * sq).unwrap().exp()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple convolutional finite impulse response filter
|
||||
pub struct FirFilter<F, T, O>
|
||||
where
|
||||
F: Mul<T, Output = O>,
|
||||
O: Add<O, Output = O> + Sum + Clone + Zero,
|
||||
{
|
||||
fir: Vec<F>,
|
||||
//taps: VecDeque<T>,
|
||||
taps: HistoryBuf<T>,
|
||||
}
|
||||
|
||||
impl<F, T, O> FirFilter<F, T, O>
|
||||
where
|
||||
T: Clone + Zero,
|
||||
F: Mul<T, Output = O> + Clone,
|
||||
O: Add<O, Output = O> + Sum + Clone + Zero,
|
||||
{
|
||||
/// Creates a filter with the given impulse response
|
||||
pub fn new(impulse_response: Fir<F>) -> Self
|
||||
{
|
||||
let len = impulse_response.0.len();
|
||||
Self {
|
||||
fir: impulse_response.0,
|
||||
taps: HistoryBuf::new(T::zero(), len),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a new sample in the filter and get its new value
|
||||
///
|
||||
/// At the beginning, the delay line starts with zeroes.
|
||||
pub fn next(&mut self, input: T) -> O
|
||||
{
|
||||
self.insert(input);
|
||||
self.filtered()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, input: T)
|
||||
{
|
||||
self.taps.push(input);
|
||||
}
|
||||
|
||||
pub fn insert_batch_ref(&mut self, input: &[T])
|
||||
{
|
||||
for x in input.iter().cloned()
|
||||
{
|
||||
self.taps.push(x);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current value of the filter
|
||||
/// given the sampels that it currently holds
|
||||
pub fn filtered(&self) -> O
|
||||
{
|
||||
let taps = self.taps.as_slice();
|
||||
Self::dot_prod(&self.fir, taps)
|
||||
}
|
||||
|
||||
pub fn dot_prod(a: &[F], b: &[T]) -> O
|
||||
{
|
||||
assert_eq!(a.len(), b.len());
|
||||
|
||||
let mut sum: [_; 4] = [O::zero(), O::zero(), O::zero(), O::zero()];
|
||||
|
||||
let (a_chunks, a_remainder) = a.as_chunks::<4>();
|
||||
let (b_chunks, b_remainder) = b.as_chunks::<4>();
|
||||
|
||||
for (x, y) in a_chunks.iter().zip(b_chunks.iter())
|
||||
{
|
||||
sum[0] = sum[0].clone() + x[0].clone() * y[0].clone();
|
||||
sum[1] = sum[1].clone() + x[1].clone() * y[1].clone();
|
||||
sum[2] = sum[2].clone() + x[2].clone() * y[2].clone();
|
||||
sum[3] = sum[3].clone() + x[3].clone() * y[3].clone();
|
||||
}
|
||||
|
||||
let mut sum = sum[0].clone() + sum[1].clone() + sum[2].clone() + sum[3].clone();
|
||||
for (x, y) in a_remainder.iter().zip(b_remainder.iter())
|
||||
{
|
||||
sum = sum + x.clone() * y.clone();
|
||||
}
|
||||
|
||||
sum
|
||||
}
|
||||
}
|
||||
|
||||
// Completely stolen from sdrpp code
|
||||
pub fn estimate_fir_length(transition_width: f64, sample_rate: f64) -> f64
|
||||
{
|
||||
3.8 * sample_rate / transition_width
|
||||
}
|
||||
|
||||
/// Root raised cosine function
|
||||
pub fn root_raised_cosine(t: f64, beta: f64, symbol_time: f64) -> f64
|
||||
{
|
||||
let eps = 1e-8;
|
||||
|
||||
if t.abs() < eps
|
||||
{
|
||||
// t = 0 special case
|
||||
return (1.0 / symbol_time.sqrt()) * (1.0 + beta * (4.0 / PI - 1.0));
|
||||
}
|
||||
|
||||
if beta > 0.0 && (t.abs() - symbol_time / (4.0 * beta)).abs() < eps
|
||||
{
|
||||
// t = ±T / (4β) special case
|
||||
let term1 = (1.0 + 2.0 / PI) * (PI / (4.0 * beta)).sin();
|
||||
let term2 = (1.0 - 2.0 / PI) * (PI / (4.0 * beta)).cos();
|
||||
return (beta / (symbol_time.sqrt() * 2.0_f64.sqrt())) * (term1 + term2);
|
||||
}
|
||||
|
||||
// General case
|
||||
let numerator = (PI * t * (1.0 - beta) / symbol_time).sin()
|
||||
+ 4.0 * beta * t / symbol_time * (PI * t * (1.0 + beta) / symbol_time).cos();
|
||||
|
||||
let denominator = PI * t * (1.0 - (4.0 * beta * t / symbol_time).powi(2)) / symbol_time;
|
||||
|
||||
(1.0 / symbol_time.sqrt()) * (numerator / denominator)
|
||||
}
|
||||
48
oxydsp-dsp/src/filtering/history_buf.rs
Normal file
48
oxydsp-dsp/src/filtering/history_buf.rs
Normal file
@ -0,0 +1,48 @@
|
||||
/// A queue that always contains the same amount of elements.
|
||||
///
|
||||
/// It intuitively record the history of `length` values of a value
|
||||
/// This implementations allows to get a single contiguous slice view on the history
|
||||
pub struct HistoryBuf<T>
|
||||
{
|
||||
buffer: Box<[T]>,
|
||||
start: usize,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
impl<T: Clone> HistoryBuf<T>
|
||||
{
|
||||
pub fn new(default: T, length: usize) -> Self
|
||||
{
|
||||
Self
|
||||
{
|
||||
buffer: vec![default; 2 * length].into_boxed_slice(),
|
||||
start: 0,
|
||||
length
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_fn(length: usize, mut f: impl FnMut(usize) -> T) -> Self
|
||||
{
|
||||
Self
|
||||
{
|
||||
buffer: (0..(2 * length)).map(|i| f(i)).collect(),
|
||||
start: 0,
|
||||
length
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, data: T)
|
||||
{
|
||||
self.buffer[self.start] = data.clone();
|
||||
self.buffer[self.start + self.length] = data.clone();
|
||||
|
||||
self.start += 1;
|
||||
self.start %= self.length;
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[T]
|
||||
{
|
||||
&self.buffer[self.start..(self.start + self.length)]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
use num::Float;
|
||||
|
||||
pub mod blocks;
|
||||
pub mod filtering;
|
||||
pub mod synthesis;
|
||||
pub mod units;
|
||||
|
||||
fn map<T: Float>(x: T, x_min: T, x_max: T, o_min: T, o_max: T) -> T
|
||||
/// Maps a float from a range onto another
|
||||
/// linearly
|
||||
pub fn map<T: Float>(x: T, x_min: T, x_max: T, o_min: T, o_max: T) -> T
|
||||
{
|
||||
((x - x_min) / (x_max - x_min)) * (o_max - o_min) + o_min
|
||||
}
|
||||
|
||||
@ -8,6 +8,21 @@ use crate::map;
|
||||
use crate::units::DigitalFrequency;
|
||||
use crate::units::Phase;
|
||||
|
||||
/// Numericaly controlled oscillator
|
||||
///
|
||||
/// ```
|
||||
/// let nco: Nco<f32> = DigitalFrequency::from_rad(2 * f32::PI).into();
|
||||
/// // Or
|
||||
/// let nco: Nco<f32> = Nco::from(DigitalFrequency::from_rad(2 * f32::PI));
|
||||
///
|
||||
/// // Rotates by 90deg per sample
|
||||
/// // The next function is from the iterator implementation
|
||||
/// assert_eq!(nco.next(), Complex::new(1., 0.));
|
||||
/// assert_eq!(nco.next(), Complex::new(0., 1.));
|
||||
/// assert_eq!(nco.next(), Complex::new(-1., 0.));
|
||||
/// assert_eq!(nco.next(), Complex::new(0., -1.));
|
||||
/// assert_eq!(nco.next(), Complex::new(1., 0.));
|
||||
/// ```
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Nco<T>
|
||||
{
|
||||
@ -21,6 +36,7 @@ pub struct Nco<T>
|
||||
|
||||
impl<T> Nco<T>
|
||||
{
|
||||
/// Creates a new Nco with a specific frequency starting at phase 0
|
||||
pub fn new(frequency: DigitalFrequency) -> Self
|
||||
{
|
||||
Self {
|
||||
@ -30,6 +46,13 @@ impl<T> Nco<T>
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current frequency of the oscillator
|
||||
pub fn frequency(&self) -> DigitalFrequency
|
||||
{
|
||||
DigitalFrequency(self.d_phase)
|
||||
}
|
||||
|
||||
/// Creates a new Nco with a specific frequency and starting phase
|
||||
pub fn with_phase(frequency: DigitalFrequency, phase: Phase) -> Self
|
||||
{
|
||||
Self {
|
||||
@ -39,16 +62,19 @@ impl<T> Nco<T>
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the current phase.
|
||||
pub fn set_phase(&mut self, phase: Phase)
|
||||
{
|
||||
self.phase = phase.0.0;
|
||||
}
|
||||
|
||||
/// Sets the current phase
|
||||
pub fn set_frequency(&mut self, frequency: DigitalFrequency)
|
||||
{
|
||||
self.d_phase = frequency.0;
|
||||
}
|
||||
|
||||
/// Steps the oscillator by one sample
|
||||
pub fn step(&mut self)
|
||||
{
|
||||
let (new, _) = self.phase.overflowing_add(self.d_phase);
|
||||
@ -58,6 +84,8 @@ impl<T> Nco<T>
|
||||
|
||||
impl<T: Float + From<f32>> Nco<T>
|
||||
{
|
||||
/// Gets the current value of the oscillator as a
|
||||
/// complex number
|
||||
pub fn sample(&self) -> Complex<T>
|
||||
{
|
||||
let t = map(
|
||||
|
||||
@ -1,16 +1,23 @@
|
||||
use std::{f64::consts::PI, ops::Neg};
|
||||
use std::f64::consts::PI;
|
||||
use std::ops::Neg;
|
||||
|
||||
use crate::map;
|
||||
|
||||
// Represents digital frequency
|
||||
/// Represents a digital, sampled frequency
|
||||
/// as radians per samples in [0; 2*pi[ range
|
||||
/// mapped to the whole [0; usize::MAX] range
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct DigitalFrequency(pub usize);
|
||||
|
||||
/// Represents an absolute phase offset
|
||||
/// as radians in [0; 2*pi[ range
|
||||
/// mapped to the whole [0; usize::MAX] range
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct Phase(pub DigitalFrequency);
|
||||
|
||||
impl DigitalFrequency
|
||||
{
|
||||
/// Creates a DigitalFrequency from rads per samples
|
||||
pub fn from_rad(radians_per_sample: f64) -> Self
|
||||
{
|
||||
// Frequnecy wraps arround : Going at 2 pi radians per second
|
||||
@ -21,16 +28,21 @@ impl DigitalFrequency
|
||||
DigitalFrequency(map(f, 0., 2. * PI, 0., usize::MAX as f64).floor() as usize)
|
||||
}
|
||||
|
||||
/// Creates a DigitalFrequency from a frequency given in hertz (s^(-1))
|
||||
/// in the context of a sample rate also given in hertz
|
||||
pub fn from_time_frequency(hertz: f64, sample_rate: f64) -> Self
|
||||
{
|
||||
Self::from_rad(map(hertz, 0., sample_rate, 0., 2. * PI))
|
||||
}
|
||||
|
||||
/// Gets the frequency as radians per sample
|
||||
pub fn as_rad(&self) -> f64
|
||||
{
|
||||
map(self.0 as f64, 0., usize::MAX as f64, 0., 2. * PI)
|
||||
}
|
||||
|
||||
/// Gets the frequency as hertz in the context of a sample rate
|
||||
/// also given in hert
|
||||
pub fn as_time_frequency(&self, sample_rate: f64) -> f64
|
||||
{
|
||||
map(self.0 as f64, 0., usize::MAX as f64, 0., sample_rate)
|
||||
@ -41,6 +53,7 @@ impl Neg for DigitalFrequency
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
/// Returns the "negative frequency"
|
||||
fn neg(self) -> Self::Output
|
||||
{
|
||||
DigitalFrequency(usize::MAX - self.0)
|
||||
|
||||
@ -4,4 +4,5 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
crossbeam-deque = "0.8.6"
|
||||
oxydsp-flowgraph-macros = { path = "./oxydsp-flowgraph-macros" }
|
||||
|
||||
@ -3,23 +3,30 @@ use zyn::ToTokens;
|
||||
use zyn::ext::AttrExt;
|
||||
use zyn::ext::FieldsExt;
|
||||
use zyn::format_ident;
|
||||
use zyn::ident;
|
||||
use zyn::parse_input;
|
||||
use zyn::syn::GenericParam;
|
||||
use zyn::syn::Index;
|
||||
use zyn::syn::Lit;
|
||||
use zyn::syn::TypeGenerics;
|
||||
use zyn::syn::parse_quote;
|
||||
use zyn::syn::spanned::Spanned;
|
||||
|
||||
mod sync;
|
||||
|
||||
#[zyn::attribute]
|
||||
pub fn sync_block(#[zyn(input)] item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
#[derive(zyn::Attribute, Clone, Copy)]
|
||||
#[zyn("sync_block", about = "Convenient derivation for synchronous blocks")]
|
||||
struct SyncBlockConfig
|
||||
{
|
||||
#[zyn(default)]
|
||||
tagged: bool,
|
||||
}
|
||||
|
||||
#[zyn::attribute]
|
||||
pub fn sync_block(#[zyn(input)] item: zyn::syn::ItemStruct, args: zyn::Args) -> zyn::TokenStream
|
||||
{
|
||||
let config = SyncBlockConfig {
|
||||
tagged: args.iter().any(|x| x.as_flag() == "tagged"),
|
||||
};
|
||||
use sync::SyncBlockImpl;
|
||||
zyn::zyn!(
|
||||
@sync_block_impl(item = item)
|
||||
@sync_block_impl(item = item, config = config)
|
||||
)
|
||||
}
|
||||
|
||||
@ -37,53 +44,67 @@ pub fn block_io(
|
||||
impl {{impl_generics}} oxydsp_flowgraph::block::BlockIO for {{ ident.clone() }} {{ type_generics }}
|
||||
{{ where_clause }}
|
||||
{
|
||||
@block_io_set_index(fields = fields.clone())
|
||||
@block_io_get_successors(fields = fields.clone())
|
||||
@block_io_counts(fields = fields.clone())
|
||||
@block_io_set_streams(fields = fields.clone())
|
||||
@block_io_create_stream(fields = fields.clone())
|
||||
@block_io_get_inputs(fields = fields.clone())
|
||||
@block_io_get_outputs(fields = fields.clone())
|
||||
@block_io_get_meta(ident = ident.clone(), fields = fields.clone())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[zyn::element]
|
||||
fn block_io_set_index(fields: zyn::syn::Fields) -> zyn::TokenStream
|
||||
fn block_io_get_inputs(fields: zyn::syn::Fields) -> zyn::TokenStream
|
||||
{
|
||||
let fields = fields.as_named().unwrap().named.clone();
|
||||
zyn::zyn!(
|
||||
fn set_index(&self, block_index: usize)
|
||||
fn get_inputs_mut(&mut self) -> Vec<&mut dyn oxydsp_flowgraph::io::edge::AnonymousIn>
|
||||
{
|
||||
use oxydsp_flowgraph::edge::BlockIOIndex;
|
||||
let mut acc = vec![];
|
||||
use oxydsp_flowgraph::block::BlockInput;
|
||||
@for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("input"))).enumerate())
|
||||
{
|
||||
self.{{field.1.ident}}.set_block_index(BlockIOIndex {block_index, port_index: {{ field.0 }} });
|
||||
acc.extend(self.{{field.1.ident}}.get_inputs_mut());
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
@for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("output"))).enumerate())
|
||||
fn get_inputs(&self) -> Vec<&dyn oxydsp_flowgraph::io::edge::AnonymousIn>
|
||||
{
|
||||
let mut acc = vec![];
|
||||
use oxydsp_flowgraph::block::BlockInput;
|
||||
@for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("input"))).enumerate())
|
||||
{
|
||||
self.{{field.1.ident}}.set_block_index(BlockIOIndex {block_index, port_index: {{ field.0 }} });
|
||||
acc.extend(self.{{field.1.ident}}.get_inputs());
|
||||
}
|
||||
acc
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[zyn::element]
|
||||
fn block_io_get_successors(fields: zyn::syn::Fields) -> zyn::TokenStream
|
||||
fn block_io_get_outputs(fields: zyn::syn::Fields) -> zyn::TokenStream
|
||||
{
|
||||
let fields = fields.as_named().unwrap().named.clone();
|
||||
zyn::zyn!(
|
||||
fn get_successors(&self) -> Vec<oxydsp_flowgraph::edge::BlockIOIndex>
|
||||
fn get_outputs_mut(&mut self) -> Vec<&mut dyn oxydsp_flowgraph::io::edge::AnonymousOut>
|
||||
{
|
||||
let mut output = vec![];
|
||||
let mut acc = vec![];
|
||||
use oxydsp_flowgraph::block::BlockOutput;
|
||||
@for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("output"))).enumerate())
|
||||
{
|
||||
if let Some(block_index) = self.{{ field.1.ident }}.get_consumer_block()
|
||||
{
|
||||
output.push(block_index);
|
||||
}
|
||||
acc.extend(self.{{field.1.ident}}.get_outputs_mut());
|
||||
}
|
||||
output
|
||||
acc
|
||||
}
|
||||
|
||||
fn get_outputs(&self) -> Vec<&dyn oxydsp_flowgraph::io::edge::AnonymousOut>
|
||||
{
|
||||
let mut acc = vec![];
|
||||
use oxydsp_flowgraph::block::BlockOutput;
|
||||
@for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("output"))).enumerate())
|
||||
{
|
||||
acc.extend(self.{{field.1.ident}}.get_outputs());
|
||||
}
|
||||
acc
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -120,103 +141,16 @@ fn block_io_get_meta(ident: zyn::syn::Ident, fields: zyn::syn::Fields) -> zyn::T
|
||||
fn get_output_type_names(&self) -> Vec<&'static str>
|
||||
{
|
||||
let mut output = Vec::new();
|
||||
use oxydsp_flowgraph::block::BlockOutput;
|
||||
@for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("output"))).enumerate())
|
||||
{
|
||||
output.push(self.{{ field.1.ident.clone() }}.get_type_name());
|
||||
output.extend(self.{{ field.1.ident.clone() }}.get_type_names());
|
||||
}
|
||||
return output;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[zyn::element]
|
||||
fn block_io_counts(fields: zyn::syn::Fields) -> zyn::TokenStream
|
||||
{
|
||||
let fields = fields.as_named().unwrap().named.clone();
|
||||
let input_count = fields
|
||||
.iter()
|
||||
.filter(|x| x.attrs.iter().any(|x| x.is("input")))
|
||||
.count();
|
||||
let output_count = fields
|
||||
.iter()
|
||||
.filter(|x| x.attrs.iter().any(|x| x.is("output")))
|
||||
.count();
|
||||
zyn::zyn!(
|
||||
fn input_count(&self) -> usize
|
||||
{
|
||||
return { { input_count } };
|
||||
}
|
||||
|
||||
fn output_count(&self) -> usize
|
||||
{
|
||||
return { { output_count } };
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[zyn::element(debug = "pretty")]
|
||||
fn block_io_set_streams(fields: zyn::syn::Fields) -> zyn::TokenStream
|
||||
{
|
||||
zyn::zyn!(
|
||||
#[allow(unreachable_code)]
|
||||
fn set_anonymous_out_stream(
|
||||
&mut self,
|
||||
output_index: usize,
|
||||
producer: oxydsp_flowgraph::io::AnonymousStreamProducer,
|
||||
)
|
||||
{
|
||||
match output_index
|
||||
{
|
||||
@for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("output"))).enumerate())
|
||||
{
|
||||
{{ field.0 }} => self.{{field.1.ident}}.set_anonymous_stream(producer),
|
||||
}
|
||||
_ => panic!("output_index out of bounds.")
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
fn set_anonymous_in_stream(&mut self, input_index: usize, consumer: oxydsp_flowgraph::io::AnonymousStreamConsumer)
|
||||
{
|
||||
match input_index
|
||||
{
|
||||
@for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("input"))).enumerate())
|
||||
{
|
||||
{{ field.0 }} => self.{{field.1.ident}}.set_anonymous_stream(consumer),
|
||||
}
|
||||
_ => panic!("output_index out of bounds.")
|
||||
};
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[zyn::element]
|
||||
fn block_io_create_stream(fields: zyn::syn::Fields) -> zyn::TokenStream
|
||||
{
|
||||
zyn::zyn!(
|
||||
#[allow(unreachable_code)]
|
||||
fn create_anonymous_stream_for(
|
||||
&mut self,
|
||||
output_index: usize,
|
||||
capacity: usize,
|
||||
) -> (
|
||||
oxydsp_flowgraph::io::AnonymousStreamProducer,
|
||||
oxydsp_flowgraph::io::AnonymousStreamConsumer,
|
||||
)
|
||||
{
|
||||
let output = match output_index
|
||||
{
|
||||
@for (field in fields.iter().filter(|x| x.attrs.iter().any(|x| x.is("output"))).enumerate())
|
||||
{
|
||||
{{ field.0 }} => self.{{ field.1.ident }}.create_anonymous_stream(capacity),
|
||||
}
|
||||
_ => panic!("output_index out of bounds."),
|
||||
};
|
||||
return output;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[zyn::element]
|
||||
fn out_inner_type(ty: zyn::syn::Type) -> zyn::TokenStream
|
||||
{
|
||||
@ -393,7 +327,7 @@ pub fn impl_iterator_for_pop_iter_tuple(input: TokenStream) -> TokenStream
|
||||
type Item = (
|
||||
@for (i in 0..count)
|
||||
{
|
||||
({{ generics[i] }}, Option<crate::tag::Tag>),
|
||||
Tagged<{{ generics[i] }}>,
|
||||
}
|
||||
);
|
||||
fn next(&mut self) -> Option<Self::Item>
|
||||
@ -405,7 +339,7 @@ pub fn impl_iterator_for_pop_iter_tuple(input: TokenStream) -> TokenStream
|
||||
Some((
|
||||
@for (i in 0..count)
|
||||
{
|
||||
self.reader.{{ Index::from(i) }}.pop_tagged().unwrap(),
|
||||
self.reader.{{ Index::from(i) }}.pop().unwrap(),
|
||||
}
|
||||
))
|
||||
}
|
||||
@ -419,3 +353,95 @@ pub fn impl_iterator_for_pop_iter_tuple(input: TokenStream) -> TokenStream
|
||||
.to_token_stream()
|
||||
.into()
|
||||
}
|
||||
// #[proc_macro]
|
||||
// pub fn generate_push_iterable_tuple_impl(input: TokenStream) -> TokenStream
|
||||
// {
|
||||
// let count = parse_input!(input as Lit);
|
||||
// let count: usize = match count
|
||||
// {
|
||||
// Lit::Int(lit_int) => lit_int.base10_parse::<usize>().unwrap(),
|
||||
// _ =>
|
||||
// {
|
||||
// return zyn::syn::Error::new(count.span(), "Must be an integer")
|
||||
// .to_compile_error()
|
||||
// .into();
|
||||
// }
|
||||
// };
|
||||
// let generics = [
|
||||
// format_ident!("A"),
|
||||
// format_ident!("B"),
|
||||
// format_ident!("C"),
|
||||
// format_ident!("D"),
|
||||
// format_ident!("E"),
|
||||
// format_ident!("F"),
|
||||
// format_ident!("G"),
|
||||
// format_ident!("H"),
|
||||
// format_ident!("I"),
|
||||
// format_ident!("J"),
|
||||
// format_ident!("K"),
|
||||
// format_ident!("L"),
|
||||
// format_ident!("M"),
|
||||
// format_ident!("N"),
|
||||
// format_ident!("O"),
|
||||
// format_ident!("P"),
|
||||
// format_ident!("Q"),
|
||||
// format_ident!("R"),
|
||||
// format_ident!("S"),
|
||||
// format_ident!("T"),
|
||||
// format_ident!("U"),
|
||||
// format_ident!("V"),
|
||||
// format_ident!("W"),
|
||||
// format_ident!("X"),
|
||||
// format_ident!("Y"),
|
||||
// format_ident!("Z"),
|
||||
// ];
|
||||
//
|
||||
// let iterator_item = zyn::zyn!(
|
||||
// (
|
||||
// @for (i in 0..count)
|
||||
// {
|
||||
// ({{ generics[i] }}, Option<Tag>),
|
||||
// }
|
||||
// )
|
||||
// )
|
||||
// .to_token_stream();
|
||||
//
|
||||
// zyn::zyn!(
|
||||
// impl<'a,
|
||||
// @for (i in 0..count)
|
||||
// {
|
||||
// {{ generics[i] }}: 'static,
|
||||
// }
|
||||
// > PushIterable<'a, {{ iterator_item }}> for (
|
||||
// @for (i in 0..count)
|
||||
// {
|
||||
// &mut Out<{{ generics[i] }}>,
|
||||
// }
|
||||
// )
|
||||
// {
|
||||
// fn push_iter<I: Iterator<Item = {{ iterator_item }}>>(
|
||||
// &'a mut self,
|
||||
// mut iter: I,
|
||||
// ) -> bool
|
||||
// {
|
||||
// @for (i in 0..count)
|
||||
// {
|
||||
// let {{ i | ident:"writer_{}" }} = self.{{ Index::from(i) }}.write();
|
||||
// }
|
||||
// let len = [
|
||||
// @for (i in 0..count)
|
||||
// {
|
||||
// {{ i | ident:"reader_{}" }}.len(),
|
||||
// }
|
||||
// ].into_iter().min().unwrap();
|
||||
//
|
||||
// for _ in 0..len
|
||||
// {
|
||||
// if let Some()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
// .to_token_stream()
|
||||
// .into()
|
||||
// }
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
use zyn::Fields;
|
||||
use zyn::ToTokens;
|
||||
use zyn::ast::at;
|
||||
use zyn::ext::AttrExt;
|
||||
use zyn::ext::FieldsExt;
|
||||
use zyn::ext::TypeExt;
|
||||
use zyn::quote;
|
||||
use zyn::quote::quote;
|
||||
use zyn::syn::Field;
|
||||
use zyn::syn::GenericParam;
|
||||
use zyn::syn::parse_quote;
|
||||
|
||||
use crate::SyncBlockConfig;
|
||||
|
||||
// Sync block
|
||||
|
||||
#[zyn::element]
|
||||
pub fn sync_block_impl(item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
pub fn sync_block_impl(item: zyn::syn::ItemStruct, config: SyncBlockConfig) -> zyn::TokenStream
|
||||
{
|
||||
zyn::zyn!(
|
||||
{{ item }}
|
||||
@ -21,37 +20,56 @@ pub fn sync_block_impl(item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
// module to keep everything clean
|
||||
mod {{ item.ident | snake | ident: "{}_synchronous_block" }}
|
||||
{
|
||||
use super::*;
|
||||
|
||||
@sync_block_view_struct(item = item.clone())
|
||||
}
|
||||
|
||||
@sync_block_syncio_impl(item = item.clone())
|
||||
@sync_block_syncio_impl(item = item.clone(), config = *config)
|
||||
@sync_block_impl_block(item = item.clone(), tagged = config.tagged)
|
||||
)
|
||||
}
|
||||
|
||||
// Block implementation for sync block
|
||||
#[zyn::element]
|
||||
fn sync_block_syncio_impl(item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
fn sync_block_syncio_impl(item: zyn::syn::ItemStruct, config: SyncBlockConfig) -> zyn::TokenStream
|
||||
{
|
||||
let view_lifetime: GenericParam = parse_quote!('view);
|
||||
let mut view_generics = item.generics.clone();
|
||||
view_generics.params.iter_mut().for_each(|x| match x
|
||||
{
|
||||
GenericParam::Lifetime(_) =>
|
||||
{}
|
||||
GenericParam::Type(type_param) => type_param
|
||||
.bounds
|
||||
.push(zyn::syn::TypeParamBound::Lifetime(parse_quote!('view))),
|
||||
GenericParam::Const(_) =>
|
||||
{}
|
||||
});
|
||||
view_generics.params.insert(0, view_lifetime);
|
||||
|
||||
let (view_impl_generics, view_type_generics, _view_where_clause) =
|
||||
let (view_impl_generics, view_type_generics, view_where_clause) =
|
||||
view_generics.split_for_impl();
|
||||
let (_impl_generics, type_generics, where_clause) = item.generics.split_for_impl();
|
||||
let (_impl_generics, type_generics, _where_clause) = item.generics.split_for_impl();
|
||||
|
||||
zyn::zyn!(
|
||||
impl {{ view_impl_generics }} oxydsp_flowgraph::block::SyncBlockIO<'view> for {{ item.ident }} {{ type_generics }}
|
||||
{{ where_clause }}
|
||||
{{ view_where_clause }}
|
||||
{
|
||||
// Path within module
|
||||
type StateView = {{ item.ident | snake | ident: "{}_synchronous_block" }}::{{ item.ident | ident:"{}View" }} {{ view_type_generics }};
|
||||
type Input = {{ sync_block_io_types(item.clone(), "input") }};
|
||||
type Output = {{ sync_block_io_types(item.clone(), "output") }};
|
||||
type Input = {{ sync_block_io_types(item.clone(), "input", config.tagged) }};
|
||||
type Output = {{ sync_block_io_types(item.clone(), "output", config.tagged) }};
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn sync_block_io_types(item: zyn::syn::ItemStruct, io: &'static str) -> zyn::TokenStream
|
||||
// Input/Output types for block
|
||||
fn sync_block_io_types(
|
||||
item: zyn::syn::ItemStruct,
|
||||
io: &'static str,
|
||||
tagged: bool,
|
||||
) -> zyn::TokenStream
|
||||
{
|
||||
let field_types = item
|
||||
.fields
|
||||
@ -71,7 +89,18 @@ fn sync_block_io_types(item: zyn::syn::ItemStruct, io: &'static str) -> zyn::Tok
|
||||
.arguments
|
||||
.clone()
|
||||
{
|
||||
zyn::syn::PathArguments::AngleBracketed(args) => args.args.to_token_stream(),
|
||||
zyn::syn::PathArguments::AngleBracketed(args) =>
|
||||
{
|
||||
let args = args.args.to_token_stream();
|
||||
if tagged
|
||||
{
|
||||
quote!(oxydsp_flowgraph::tag::Tagged<#args>).into_token_stream()
|
||||
}
|
||||
else
|
||||
{
|
||||
args
|
||||
}
|
||||
}
|
||||
zyn::syn::PathArguments::None => panic!(),
|
||||
zyn::syn::PathArguments::Parenthesized(_) => panic!(),
|
||||
}
|
||||
@ -98,15 +127,26 @@ fn sync_block_io_types(item: zyn::syn::ItemStruct, io: &'static str) -> zyn::Tok
|
||||
.into_token_stream()
|
||||
}
|
||||
|
||||
// View struct for sync block
|
||||
#[zyn::element]
|
||||
fn sync_block_view_struct(item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
{
|
||||
// Create view liftime to add to struct definition
|
||||
let lifetime: GenericParam = parse_quote!('view);
|
||||
let mut generics = item.generics.clone();
|
||||
generics.params.iter_mut().for_each(|x| match x
|
||||
{
|
||||
GenericParam::Lifetime(_) =>
|
||||
{}
|
||||
GenericParam::Type(type_param) => type_param
|
||||
.bounds
|
||||
.push(zyn::syn::TypeParamBound::Lifetime(parse_quote!('view))),
|
||||
GenericParam::Const(_) =>
|
||||
{}
|
||||
});
|
||||
generics.params.insert(0, lifetime);
|
||||
|
||||
let (_impl_generics, type_generics, where_clause) = generics.split_for_impl();
|
||||
let (impl_generics, _type_generics, where_clause) = generics.split_for_impl();
|
||||
let fields = &item.fields.as_named().unwrap().named;
|
||||
|
||||
let mut state_fields = vec![];
|
||||
@ -151,8 +191,7 @@ fn sync_block_view_struct(item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
.filter(|tokens| !tokens.is_empty());
|
||||
|
||||
zyn::zyn!(
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct {{ item.ident | ident:"{}View" }} {{ type_generics }}
|
||||
pub struct {{ item.ident | ident:"{}View" }} {{ impl_generics }}
|
||||
{{ where_clause }}
|
||||
{
|
||||
@for (field in state_fields.iter())
|
||||
@ -169,8 +208,40 @@ fn sync_block_view_struct(item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
)
|
||||
}
|
||||
|
||||
// Instantiates the view struct
|
||||
fn sync_block_make_view_struct(item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
{
|
||||
let fields = &item.fields.as_named().unwrap().named;
|
||||
|
||||
let mut state_fields = vec![];
|
||||
for field in fields.iter()
|
||||
{
|
||||
let mut f = field.clone();
|
||||
if f.attrs.iter().any(|x| x.is("input") || x.is("output"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let tk = field.ty.clone().into_token_stream();
|
||||
f.ty = parse_quote!(&'view mut #tk);
|
||||
state_fields.push(f);
|
||||
}
|
||||
|
||||
zyn::zyn!(
|
||||
{{item.ident | snake | ident:"{}_synchronous_block" }}::{{ item.ident | ident:"{}View" }} {
|
||||
@for (field in state_fields)
|
||||
{
|
||||
{{field.ident}}: &mut self.{{ field.ident }},
|
||||
}
|
||||
_sync_block_phantom: Default::default(),
|
||||
}
|
||||
)
|
||||
.into_token_stream()
|
||||
}
|
||||
|
||||
// Impl Block for syncio
|
||||
#[zyn::element]
|
||||
fn sync_block_impl_block(item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
fn sync_block_impl_block(item: zyn::syn::ItemStruct, tagged: bool) -> zyn::TokenStream
|
||||
{
|
||||
let item2 = item.clone();
|
||||
let (impl_generics, type_generics, where_clause) = item2.generics.split_for_impl();
|
||||
@ -194,19 +265,31 @@ fn sync_block_impl_block(item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
{
|
||||
fn work(&mut self) -> oxydsp_flowgraph::block::BlockResult
|
||||
{
|
||||
let state = {{ sync_block_make_view_struct(item.clone()) }};
|
||||
|
||||
use oxydsp_flowgraph::tag::TagMergable;
|
||||
// Get writers from outputs
|
||||
let mut max_len = usize::MAX;
|
||||
@for (out_field in output_fields.iter())
|
||||
{
|
||||
let {{ out_field.ident.clone().unwrap() | ident: "{}_writer"}} = self.{{ out_field.ident }}.write();
|
||||
max_len = max_len.min({{ out_field.ident.clone().unwrap() | ident: "{}_writer"}}.len());
|
||||
}
|
||||
|
||||
// Compute max_len
|
||||
let max_len = *([
|
||||
usize::MAX,
|
||||
@for (out_field in output_fields.iter())
|
||||
{
|
||||
{{ out_field.ident.clone().unwrap() | ident: "{}_writer"}}.len(),
|
||||
}
|
||||
].iter().min().unwrap());
|
||||
|
||||
|
||||
@if (!input_fields.is_empty())
|
||||
{
|
||||
@sync_block_block_impl_with_inputs(item = item.clone(), input_fields = input_fields.clone(), output_fields = output_fields.clone())
|
||||
@sync_block_block_impl_with_inputs(item = item.clone(), input_fields = input_fields.clone(), output_fields = output_fields.clone(), tagged = *tagged)
|
||||
}
|
||||
@else
|
||||
{
|
||||
@sync_block_block_impl_without_inputs(item = item.clone(), output_fields = output_fields.clone(), tagged = *tagged)
|
||||
}
|
||||
|
||||
oxydsp_flowgraph::block::BlockResult::Ok
|
||||
@ -215,118 +298,59 @@ fn sync_block_impl_block(item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
)
|
||||
}
|
||||
|
||||
// Impl Block for syncio (no inputs)
|
||||
#[zyn::element]
|
||||
fn sync_block_block_impl_with_inputs(
|
||||
fn sync_block_block_impl_without_inputs(
|
||||
item: zyn::syn::ItemStruct,
|
||||
output_fields: Vec<Field>,
|
||||
tagged: bool,
|
||||
) -> zyn::TokenStream
|
||||
{
|
||||
zyn::zyn!(
|
||||
|
||||
for _ in 0..max_len
|
||||
{
|
||||
@if (output_fields.len() == 1)
|
||||
{
|
||||
let {{output_fields[0].ident.clone().unwrap() | ident:"{}_element"}}
|
||||
}
|
||||
@else
|
||||
{
|
||||
let (@for (out_field in output_fields.iter())
|
||||
{
|
||||
{{out_field.ident.clone().unwrap() | ident:"{}_element"}},
|
||||
}
|
||||
)
|
||||
}
|
||||
= <Self as SyncBlockIO>::sync_work(state, ()).unwrap();
|
||||
}
|
||||
|
||||
// Iterate on inputs
|
||||
(
|
||||
@for (in_field in input_fields.iter())
|
||||
{
|
||||
&mut self.{{ in_field.ident }},
|
||||
}
|
||||
).pop_iter()
|
||||
.zip(0..max_len)
|
||||
.for_each(
|
||||
// Deconstruct foreach arguments
|
||||
|
|
||||
(
|
||||
(@for (in_field in input_fields.iter())
|
||||
{
|
||||
({{in_field.ident.clone().unwrap() | ident:"{}_element"}},
|
||||
{{in_field.ident.clone().unwrap() | ident:"{}_tag_opt"}}),
|
||||
}),
|
||||
_ // Ignore index
|
||||
)
|
||||
|
|
||||
{
|
||||
// Create output tag
|
||||
let tag = oxydsp_flowgraph::tag::merge_tag_opts([
|
||||
@for (in_field in input_fields.iter())
|
||||
{
|
||||
{{in_field.ident.clone().unwrap() | ident:"{}_tag_opt"}},
|
||||
},
|
||||
]);
|
||||
|
||||
// Compute output sample
|
||||
@if (output_fields.is_empty())
|
||||
{
|
||||
let _
|
||||
}
|
||||
@else if (output_fields.len() == 1)
|
||||
{
|
||||
let {{output_fields[0].ident.clone().unwrap() | ident:"{}_element"}}
|
||||
}
|
||||
@else
|
||||
{
|
||||
let (@for (out_field in output_fields.iter())
|
||||
{
|
||||
{{out_field.ident.clone().unwrap() | ident:"{}_element"}},
|
||||
}
|
||||
)
|
||||
}
|
||||
= <Self as SyncBlockIO>::sync_work(state,
|
||||
@if (input_fields.len() == 1)
|
||||
{
|
||||
{{input_fields[0].ident.clone().unwrap() | ident:"{}_element"}},
|
||||
}
|
||||
@else
|
||||
{
|
||||
(@for (in_field in input_fields.iter())
|
||||
{
|
||||
{{in_field.ident.clone().unwrap() | ident:"{}_element"}},
|
||||
}
|
||||
)
|
||||
}
|
||||
).unwrap();
|
||||
// Get outputs
|
||||
let state = {{ sync_block_make_view_struct(item.clone()) }};
|
||||
let @sync_block_output_descons(output_fields = output_fields.clone(), tagged = *tagged) =
|
||||
<Self as oxydsp_flowgraph::block::SyncBlock>::sync_work(state, ()).unwrap();
|
||||
|
||||
// Now the output samples must be sent to their resepective outputs
|
||||
@for (out_field in output_fields.iter())
|
||||
{
|
||||
{{ out_field.ident.clone().unwrap() | ident: "{}_writer"}}.push(
|
||||
(
|
||||
@if (*tagged)
|
||||
{
|
||||
let _ = {{ out_field.ident.clone().unwrap() | ident: "{}_writer"}}.push(
|
||||
oxydsp_flowgraph::tag::Tagged(
|
||||
{{ out_field.ident.clone().unwrap() | ident: "{}_element"}},
|
||||
tag.clone()
|
||||
{{ out_field.ident.clone().unwrap() | ident: "{}_tag_opt"}},
|
||||
)
|
||||
);
|
||||
} @else
|
||||
{
|
||||
let _ = {{ out_field.ident.clone().unwrap() | ident: "{}_writer"}}.push(
|
||||
oxydsp_flowgraph::tag::Tagged(
|
||||
{{ out_field.ident.clone().unwrap() | ident: "{}_element"}},
|
||||
None,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
}
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// Impl Block for syncio (with inputs)
|
||||
#[zyn::element]
|
||||
fn sync_block_block_impl_with_inputs(
|
||||
item: zyn::syn::ItemStruct,
|
||||
input_fields: Vec<Field>,
|
||||
output_fields: Vec<Field>,
|
||||
tagged: bool,
|
||||
) -> zyn::TokenStream
|
||||
{
|
||||
zyn::zyn!(
|
||||
|
||||
use oxydsp_flowgraph::io::PopIterable;
|
||||
// Iterate on inputs
|
||||
(
|
||||
@for (in_field in input_fields.iter())
|
||||
@ -334,69 +358,50 @@ fn sync_block_block_impl_with_inputs(
|
||||
&mut self.{{ in_field.ident }},
|
||||
}
|
||||
).pop_iter()
|
||||
.zip(0..max_len)
|
||||
.take(max_len)
|
||||
.for_each(
|
||||
// Deconstruct foreach arguments
|
||||
|
|
||||
(
|
||||
(@for (in_field in input_fields.iter())
|
||||
{
|
||||
({{in_field.ident.clone().unwrap() | ident:"{}_element"}},
|
||||
oxydsp_flowgraph::tag::Tagged({{in_field.ident.clone().unwrap() | ident:"{}_element"}},
|
||||
{{in_field.ident.clone().unwrap() | ident:"{}_tag_opt"}}),
|
||||
}),
|
||||
_ // Ignore index
|
||||
)
|
||||
})
|
||||
|
|
||||
{
|
||||
// Create output tag
|
||||
let tag = oxydsp_flowgraph::tag::merge_tag_opts([
|
||||
let common_tag = oxydsp_flowgraph::tag::Tag::from_tag_opts([
|
||||
@for (in_field in input_fields.iter())
|
||||
{
|
||||
{{in_field.ident.clone().unwrap() | ident:"{}_tag_opt"}},
|
||||
},
|
||||
&{{in_field.ident.clone().unwrap() | ident:"{}_tag_opt"}},
|
||||
}
|
||||
]);
|
||||
|
||||
let state = {{ sync_block_make_view_struct(item.clone()) }};
|
||||
// Compute output sample
|
||||
@if (output_fields.is_empty())
|
||||
{
|
||||
let _
|
||||
}
|
||||
@else if (output_fields.len() == 1)
|
||||
{
|
||||
let {{output_fields[0].ident.clone().unwrap() | ident:"{}_element"}}
|
||||
}
|
||||
@else
|
||||
{
|
||||
let (@for (out_field in output_fields.iter())
|
||||
{
|
||||
{{out_field.ident.clone().unwrap() | ident:"{}_element"}},
|
||||
}
|
||||
)
|
||||
}
|
||||
= <Self as SyncBlockIO>::sync_work(state,
|
||||
@if (input_fields.len() == 1)
|
||||
{
|
||||
{{input_fields[0].ident.clone().unwrap() | ident:"{}_element"}},
|
||||
}
|
||||
@else
|
||||
{
|
||||
(@for (in_field in input_fields.iter())
|
||||
{
|
||||
{{in_field.ident.clone().unwrap() | ident:"{}_element"}},
|
||||
}
|
||||
)
|
||||
}
|
||||
).unwrap();
|
||||
let @sync_block_output_descons(output_fields = output_fields.clone(), tagged = *tagged)
|
||||
= <Self as oxydsp_flowgraph::block::SyncBlock>::sync_work(state, @sync_block_input_cons(input_fields = input_fields.clone(), tagged = *tagged)).unwrap();
|
||||
|
||||
// Now the output samples must be sent to their resepective outputs
|
||||
@for (out_field in output_fields.iter())
|
||||
{
|
||||
{{ out_field.ident.clone().unwrap() | ident: "{}_writer"}}.push(
|
||||
(
|
||||
@if (*tagged)
|
||||
{
|
||||
let _ = {{ out_field.ident.clone().unwrap() | ident: "{}_writer"}}.push(
|
||||
oxydsp_flowgraph::tag::Tagged(
|
||||
{{ out_field.ident.clone().unwrap() | ident: "{}_element"}},
|
||||
tag.clone()
|
||||
{{ out_field.ident.clone().unwrap() | ident: "{}_tag_opt"}}.merge(&common_tag),
|
||||
)
|
||||
);
|
||||
} @else
|
||||
{
|
||||
let _ = {{ out_field.ident.clone().unwrap() | ident: "{}_writer"}}.push(
|
||||
oxydsp_flowgraph::tag::Tagged(
|
||||
{{ out_field.ident.clone().unwrap() | ident: "{}_element"}},
|
||||
common_tag,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
@ -405,32 +410,110 @@ fn sync_block_block_impl_with_inputs(
|
||||
)
|
||||
}
|
||||
|
||||
fn sync_block_make_view_struct(item: zyn::syn::ItemStruct) -> zyn::TokenStream
|
||||
#[zyn::element]
|
||||
fn sync_block_output_descons(output_fields: Vec<Field>, tagged: bool) -> zyn::TokenStream
|
||||
{
|
||||
let fields = &item.fields.as_named().unwrap().named;
|
||||
|
||||
let mut state_fields = vec![];
|
||||
for field in fields.iter()
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
if *tagged
|
||||
{
|
||||
let mut f = field.clone();
|
||||
if f.attrs.iter().any(|x| x.is("input") || x.is("output"))
|
||||
// If tagged : deconstruct tags
|
||||
if output_fields.is_empty()
|
||||
{
|
||||
continue;
|
||||
zyn::zyn!(_)
|
||||
}
|
||||
else if output_fields.len() == 1
|
||||
{
|
||||
zyn::zyn!(
|
||||
oxydsp_flowgraph::tag::Tagged({{output_fields[0].ident.clone().unwrap() | ident:"{}_element"}}, {{output_fields[0].ident.clone().unwrap() | ident:"{}_tag_opt"}})
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
zyn::zyn!(
|
||||
(@for (in_field in output_fields.iter())
|
||||
{
|
||||
oxydsp_flowgraph::tag::Tagged({{in_field.ident.clone().unwrap() | ident:"{}_element"}}, {{in_field.ident.clone().unwrap() | ident:"{}_tag_opt"}}.clone()),
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let tk = field.ty.clone().into_token_stream();
|
||||
f.ty = parse_quote!(&'view mut #tk);
|
||||
state_fields.push(f);
|
||||
}
|
||||
|
||||
zyn::zyn!(
|
||||
{{ item.ident | ident:"{}View" }} {
|
||||
@for (field in state_fields)
|
||||
{
|
||||
{{field.ident}}: &mut self.{{ field.ident }},
|
||||
}
|
||||
_sync_block_phantom: Default::default(),
|
||||
else
|
||||
{
|
||||
// Otherwise just get the output element
|
||||
if output_fields.is_empty()
|
||||
{
|
||||
zyn::zyn!(_)
|
||||
}
|
||||
)
|
||||
.into_token_stream()
|
||||
else if output_fields.len() == 1
|
||||
{
|
||||
zyn::zyn!(
|
||||
{{output_fields[0].ident.clone().unwrap() | ident:"{}_element"}}
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
zyn::zyn!(
|
||||
(@for (in_field in output_fields.iter())
|
||||
{
|
||||
{{in_field.ident.clone().unwrap() | ident:"{}_element"}},
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[zyn::element]
|
||||
fn sync_block_input_cons(input_fields: Vec<Field>, tagged: bool) -> zyn::TokenStream
|
||||
{
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
if *tagged
|
||||
{
|
||||
// If tagged : deconstruct tags
|
||||
if input_fields.is_empty()
|
||||
{
|
||||
zyn::zyn!(())
|
||||
}
|
||||
else if input_fields.len() == 1
|
||||
{
|
||||
zyn::zyn!(
|
||||
oxydsp_flowgraph::tag::Tagged({{input_fields[0].ident.clone().unwrap() | ident:"{}_element"}}, {{input_fields[0].ident.clone().unwrap() | ident:"{}_tag_opt"}})
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
zyn::zyn!(
|
||||
(@for (in_field in input_fields.iter())
|
||||
{
|
||||
oxydsp_flowgraph::tag::Tagged({{in_field.ident.clone().unwrap() | ident:"{}_element"}}, {{in_field.ident.clone().unwrap() | ident:"{}_tag_opt"}}.clone()),
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise just get the output element
|
||||
if input_fields.is_empty()
|
||||
{
|
||||
zyn::zyn!(_)
|
||||
}
|
||||
else if input_fields.len() == 1
|
||||
{
|
||||
zyn::zyn!(
|
||||
{{input_fields[0].ident.clone().unwrap() | ident:"{}_element"}}
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
zyn::zyn!(
|
||||
(@for (in_field in input_fields.iter())
|
||||
{
|
||||
{{in_field.ident.clone().unwrap() | ident:"{}_element"}},
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
use crate::edge::BlockIOIndex;
|
||||
use crate::io::AnonymousStreamConsumer;
|
||||
use crate::io::AnonymousStreamProducer;
|
||||
use crate::io::edge::AnonymousIn;
|
||||
use crate::io::edge::AnonymousOut;
|
||||
use crate::io::In;
|
||||
use crate::io::Out;
|
||||
use crate::io::edge::BlockIOIndex;
|
||||
|
||||
pub enum BlockResult
|
||||
{
|
||||
@ -9,32 +11,66 @@ pub enum BlockResult
|
||||
|
||||
// Signifies that the block finished its work
|
||||
// Running it again would be useless
|
||||
// This triggers the graph shutdown
|
||||
Terminated,
|
||||
|
||||
// Kill graph
|
||||
Exit,
|
||||
}
|
||||
|
||||
pub trait BlockInput
|
||||
{
|
||||
fn get_inputs_mut(&mut self) -> Vec<&mut dyn AnonymousIn>;
|
||||
fn get_inputs(&self) -> Vec<&dyn AnonymousIn>;
|
||||
|
||||
// Meta information
|
||||
fn get_type_names(&self) -> Vec<&'static str>;
|
||||
}
|
||||
|
||||
pub trait BlockOutput
|
||||
{
|
||||
fn get_outputs_mut(&mut self) -> Vec<&mut dyn AnonymousOut>;
|
||||
fn get_outputs(&self) -> Vec<&dyn AnonymousOut>;
|
||||
|
||||
// Meta information
|
||||
fn get_type_names(&self) -> Vec<&'static str>;
|
||||
}
|
||||
|
||||
pub trait BlockIO
|
||||
{
|
||||
fn get_inputs_mut(&mut self) -> Vec<&mut dyn AnonymousIn>;
|
||||
fn get_outputs_mut(&mut self) -> Vec<&mut dyn AnonymousOut>;
|
||||
|
||||
fn get_inputs(&self) -> Vec<&dyn AnonymousIn>;
|
||||
fn get_outputs(&self) -> Vec<&dyn AnonymousOut>;
|
||||
|
||||
fn get_successors(&self) -> Vec<BlockIOIndex>
|
||||
{
|
||||
self.get_outputs()
|
||||
.iter()
|
||||
.map(|x| x.get_consumer_block().unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Get all of the BlockIOIndices (block index + port) that
|
||||
// this blocks can send data to.
|
||||
fn get_successors(&self) -> Vec<BlockIOIndex>;
|
||||
|
||||
// Sets the index of the current blocks on the shared edges
|
||||
fn set_index(&self, block_index: usize);
|
||||
|
||||
// Number of input/output ports
|
||||
fn input_count(&self) -> usize;
|
||||
fn output_count(&self) -> usize;
|
||||
|
||||
// Stream managment
|
||||
fn set_anonymous_out_stream(&mut self, output_index: usize, producer: AnonymousStreamProducer);
|
||||
fn set_anonymous_in_stream(&mut self, input_index: usize, consumer: AnonymousStreamConsumer);
|
||||
|
||||
fn create_anonymous_stream_for(
|
||||
&mut self,
|
||||
output_index: usize,
|
||||
capacity: usize,
|
||||
) -> (AnonymousStreamProducer, AnonymousStreamConsumer);
|
||||
// fn get_successors(&self) -> Vec<BlockIOIndex>;
|
||||
//
|
||||
// // Sets the index of the current blocks on the shared edges
|
||||
// fn set_index(&self, block_index: usize);
|
||||
//
|
||||
// // Number of input/output ports
|
||||
// fn input_count(&self) -> usize;
|
||||
// fn output_count(&self) -> usize;
|
||||
//
|
||||
// // Stream managment
|
||||
// fn set_anonymous_out_stream(&mut self, output_index: usize, producer: AnonymousStreamProducer);
|
||||
// fn set_anonymous_in_stream(&mut self, input_index: usize, consumer: AnonymousStreamConsumer);
|
||||
//
|
||||
// fn create_anonymous_stream_for(
|
||||
// &mut self,
|
||||
// output_index: usize,
|
||||
// capacity: usize,
|
||||
// ) -> (AnonymousStreamProducer, AnonymousStreamConsumer);
|
||||
|
||||
// Meta information
|
||||
fn get_block_name(&self) -> &'static str;
|
||||
@ -65,3 +101,173 @@ pub trait SyncBlock<'view>: SyncBlockIO<'view>
|
||||
pub trait GraphableBlock: Block + BlockIO {}
|
||||
|
||||
impl<T> GraphableBlock for T where T: Block + BlockIO {}
|
||||
|
||||
impl<T: 'static> BlockInput for In<T>
|
||||
{
|
||||
fn get_inputs_mut(&mut self) -> Vec<&mut dyn AnonymousIn>
|
||||
{
|
||||
vec![self]
|
||||
}
|
||||
|
||||
fn get_inputs(&self) -> Vec<&dyn AnonymousIn>
|
||||
{
|
||||
vec![self]
|
||||
}
|
||||
|
||||
fn get_type_names(&self) -> Vec<&'static str>
|
||||
{
|
||||
vec![std::any::type_name::<T>()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: BlockInput> BlockInput for Option<I>
|
||||
{
|
||||
fn get_inputs_mut(&mut self) -> Vec<&mut dyn AnonymousIn>
|
||||
{
|
||||
if let Some(input) = self
|
||||
{
|
||||
input.get_inputs_mut()
|
||||
}
|
||||
else
|
||||
{
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_inputs(&self) -> Vec<&dyn AnonymousIn>
|
||||
{
|
||||
if let Some(input) = self
|
||||
{
|
||||
input.get_inputs()
|
||||
}
|
||||
else
|
||||
{
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_type_names(&self) -> Vec<&'static str>
|
||||
{
|
||||
if let Some(input) = self
|
||||
{
|
||||
input.get_type_names()
|
||||
}
|
||||
else
|
||||
{
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: BlockInput, const N: usize> BlockInput for [I; N]
|
||||
{
|
||||
fn get_inputs(&self) -> Vec<&dyn AnonymousIn>
|
||||
{
|
||||
let mut output = vec![];
|
||||
for input in self
|
||||
{
|
||||
output.extend(input.get_inputs());
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
fn get_inputs_mut(&mut self) -> Vec<&mut dyn AnonymousIn>
|
||||
{
|
||||
let mut output = vec![];
|
||||
for input in self
|
||||
{
|
||||
output.extend(input.get_inputs_mut());
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
fn get_type_names(&self) -> Vec<&'static str>
|
||||
{
|
||||
vec![std::any::type_name::<I>(); N]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> BlockOutput for Out<T>
|
||||
{
|
||||
fn get_outputs_mut(&mut self) -> Vec<&mut dyn AnonymousOut>
|
||||
{
|
||||
vec![self]
|
||||
}
|
||||
|
||||
fn get_outputs(&self) -> Vec<&dyn AnonymousOut>
|
||||
{
|
||||
vec![self]
|
||||
}
|
||||
|
||||
fn get_type_names(&self) -> Vec<&'static str>
|
||||
{
|
||||
vec![std::any::type_name::<T>()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: BlockOutput> BlockOutput for Option<I>
|
||||
{
|
||||
fn get_outputs_mut(&mut self) -> Vec<&mut dyn AnonymousOut>
|
||||
{
|
||||
if let Some(output) = self
|
||||
{
|
||||
output.get_outputs_mut()
|
||||
}
|
||||
else
|
||||
{
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_outputs(&self) -> Vec<&dyn AnonymousOut>
|
||||
{
|
||||
if let Some(output) = self
|
||||
{
|
||||
output.get_outputs()
|
||||
}
|
||||
else
|
||||
{
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_type_names(&self) -> Vec<&'static str>
|
||||
{
|
||||
if let Some(input) = self
|
||||
{
|
||||
input.get_type_names()
|
||||
}
|
||||
else
|
||||
{
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: BlockOutput, const N: usize> BlockOutput for [I; N]
|
||||
{
|
||||
fn get_outputs_mut(&mut self) -> Vec<&mut dyn AnonymousOut>
|
||||
{
|
||||
let mut result = vec![];
|
||||
for output in self
|
||||
{
|
||||
result.extend(output.get_outputs_mut());
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn get_outputs(&self) -> Vec<&dyn AnonymousOut>
|
||||
{
|
||||
let mut result = vec![];
|
||||
for output in self
|
||||
{
|
||||
result.extend(output.get_outputs());
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn get_type_names(&self) -> Vec<&'static str>
|
||||
{
|
||||
vec![std::any::type_name::<I>(); N]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
use std::any::Any;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Edge
|
||||
{
|
||||
// Represents the index of the block owning the Out end in the graph
|
||||
// And the the output index within that block
|
||||
pub from: Option<BlockIOIndex>,
|
||||
|
||||
// Represents the index of the block owning the In end in the graph
|
||||
// And the the input index within that block
|
||||
pub to: Option<BlockIOIndex>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct BlockIOIndex
|
||||
{
|
||||
pub block_index: usize,
|
||||
pub port_index: usize,
|
||||
}
|
||||
@ -1,6 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use crossbeam_deque::Steal;
|
||||
use crossbeam_deque::Worker;
|
||||
|
||||
use crate::block::GraphableBlock;
|
||||
use crate::io::edge::BlockIOIndex;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! flowgraph
|
||||
@ -8,7 +14,7 @@ macro_rules! flowgraph
|
||||
($($x:ident),* $(,)?) =>
|
||||
{
|
||||
{
|
||||
let mut flowgraph = FlowGraph::new();
|
||||
let mut flowgraph = oxydsp_flowgraph::graph::FlowGraph::new();
|
||||
$(
|
||||
flowgraph.add_block($x);
|
||||
)*
|
||||
@ -17,6 +23,21 @@ macro_rules! flowgraph
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RunningGraph
|
||||
{
|
||||
worker_handles: Vec<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl RunningGraph
|
||||
{
|
||||
pub fn join(self)
|
||||
{
|
||||
self.worker_handles.into_iter().for_each(|j| {
|
||||
let _ = j.join();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FlowGraph
|
||||
{
|
||||
blocks: Vec<Box<dyn GraphableBlock + Send + 'static>>,
|
||||
@ -31,52 +52,157 @@ impl FlowGraph
|
||||
|
||||
pub fn add_block<T: GraphableBlock + Send + 'static>(&mut self, block: T)
|
||||
{
|
||||
block.set_index(self.blocks.len());
|
||||
block.get_inputs().iter().enumerate().for_each(|(i, x)| {
|
||||
x.set_index(BlockIOIndex {
|
||||
block_index: self.blocks.len(),
|
||||
port_index: i,
|
||||
})
|
||||
});
|
||||
block.get_outputs().iter().enumerate().for_each(|(i, x)| {
|
||||
x.set_index(BlockIOIndex {
|
||||
block_index: self.blocks.len(),
|
||||
port_index: i,
|
||||
})
|
||||
});
|
||||
self.blocks.push(Box::new(block));
|
||||
}
|
||||
|
||||
pub fn run(mut self) -> JoinHandle<()>
|
||||
// pub fn run(mut self, thread_count: usize) -> RunningGraph
|
||||
// {
|
||||
// self.populate_edges();
|
||||
//
|
||||
// let mut worker_queues = (0..thread_count).map(|_| vec![]).collect::<Vec<_>>();
|
||||
//
|
||||
// for (i, block) in self.blocks.into_iter().enumerate()
|
||||
// {
|
||||
// worker_queues[i % thread_count].push(block);
|
||||
// }
|
||||
//
|
||||
// let running = Arc::new(AtomicBool::new(true));
|
||||
// let worker_handles = worker_queues
|
||||
// .into_iter()
|
||||
// .map(|mut queue| {
|
||||
// let running = running.clone();
|
||||
// std::thread::spawn(move || {
|
||||
// 'outer: while running.load(std::sync::atomic::Ordering::Relaxed)
|
||||
// {
|
||||
// for block in queue.iter_mut()
|
||||
// {
|
||||
// match block.work()
|
||||
// {
|
||||
// crate::block::BlockResult::Ok =>
|
||||
// {
|
||||
// // Reschedule block
|
||||
// }
|
||||
// crate::block::BlockResult::Terminated =>
|
||||
// { // DROP BLOCK
|
||||
// }
|
||||
// crate::block::BlockResult::Exit =>
|
||||
// {
|
||||
// println!("KILLING GRAPH");
|
||||
// break 'outer;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// running.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
// })
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
// RunningGraph { worker_handles }
|
||||
// }
|
||||
|
||||
pub fn run(mut self, thread_count: usize) -> RunningGraph
|
||||
{
|
||||
self.populate_edges();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
'outer: loop
|
||||
{
|
||||
for x in self.blocks.iter_mut()
|
||||
{
|
||||
match x.work()
|
||||
let worker_queues = (0..thread_count)
|
||||
.map(|_| Worker::<Box<dyn GraphableBlock + Send + 'static>>::new_fifo())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
worker_queues
|
||||
.iter()
|
||||
.cycle()
|
||||
.zip(self.blocks)
|
||||
.for_each(|(worker, block)| worker.push(block));
|
||||
let stealers = worker_queues
|
||||
.iter()
|
||||
.map(|x| x.stealer())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let worker_handles = worker_queues
|
||||
.into_iter()
|
||||
.map(|queue| {
|
||||
let stealers = stealers.clone();
|
||||
let running = running.clone();
|
||||
std::thread::spawn(move || {
|
||||
'outer: while running.load(std::sync::atomic::Ordering::Relaxed)
|
||||
{
|
||||
crate::block::BlockResult::Ok =>
|
||||
{}
|
||||
crate::block::BlockResult::Terminated =>
|
||||
// Try to get a job
|
||||
let mut block = queue.pop().unwrap_or_else(|| {
|
||||
std::iter::repeat_with(|| {
|
||||
stealers
|
||||
.iter()
|
||||
.map(|stealer| stealer.steal_batch_and_pop(&queue))
|
||||
.collect::<Steal<_>>()
|
||||
.success()
|
||||
})
|
||||
.find(|x| x.is_some())
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
match block.work()
|
||||
{
|
||||
break 'outer;
|
||||
crate::block::BlockResult::Ok =>
|
||||
{
|
||||
// Reschedule block
|
||||
queue.push(block);
|
||||
}
|
||||
crate::block::BlockResult::Terminated =>
|
||||
{ // DROP BLOCK
|
||||
}
|
||||
crate::block::BlockResult::Exit =>
|
||||
{
|
||||
println!("KILLING GRAPH");
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..10_000
|
||||
{
|
||||
for x in self.blocks.iter_mut()
|
||||
{
|
||||
x.work();
|
||||
}
|
||||
}
|
||||
})
|
||||
running.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
RunningGraph { worker_handles }
|
||||
}
|
||||
|
||||
fn populate_edges(&mut self)
|
||||
{
|
||||
for block_index in 0..self.blocks.len()
|
||||
{
|
||||
let successors = self.blocks[block_index].get_successors();
|
||||
for (output_index, succ_id) in successors.iter().enumerate()
|
||||
let outputs = self.blocks[block_index].get_outputs_mut();
|
||||
let mut rxs = vec![];
|
||||
|
||||
for output in outputs.into_iter()
|
||||
{
|
||||
let (tx, rx) =
|
||||
self.blocks[block_index].create_anonymous_stream_for(output_index, 4096);
|
||||
self.blocks[block_index].set_anonymous_out_stream(output_index, tx);
|
||||
self.blocks[succ_id.block_index].set_anonymous_in_stream(succ_id.port_index, rx);
|
||||
//let (tx, rx) = output.create_anonymous_stream(4096);
|
||||
let (tx, rx) = output.create_anonymous_stream(8192);
|
||||
//let (tx, rx) = output.create_anonymous_stream(65536);
|
||||
|
||||
output.set_anonymous_stream(tx);
|
||||
rxs.push((
|
||||
output
|
||||
.get_consumer_block()
|
||||
.expect("Non existent destination block."),
|
||||
rx,
|
||||
))
|
||||
}
|
||||
|
||||
for (index, rx) in rxs.into_iter()
|
||||
{
|
||||
self.blocks[index.block_index].get_inputs_mut()[index.port_index]
|
||||
.set_anonymous_stream(rx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,49 +1,327 @@
|
||||
use std::any::Any;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use oxydsp_flowgraph_macros::generate_pop_iterable_tuple_impl;
|
||||
use oxydsp_flowgraph_macros::impl_iterator_for_pop_iter_tuple;
|
||||
|
||||
use crate::edge::BlockIOIndex;
|
||||
use crate::edge::Edge;
|
||||
use crate::stream::StreamConsumer;
|
||||
use crate::stream::StreamProducer;
|
||||
use crate::stream::StreamReader;
|
||||
use crate::stream::StreamWriter;
|
||||
use crate::stream::{self};
|
||||
use crate::tag::Tag;
|
||||
use crate::tag::TagSlot;
|
||||
use crate::tag::Tagged;
|
||||
|
||||
pub mod edge;
|
||||
|
||||
use crate::io::edge::Edge;
|
||||
|
||||
/// Represents a input port for a block
|
||||
pub struct In<T>
|
||||
{
|
||||
stream: Option<StreamConsumer<T>>,
|
||||
tag_stream: Option<StreamConsumer<Tag>>,
|
||||
tag_stream: Option<StreamConsumer<TagSlot>>,
|
||||
|
||||
// Will rarely be accessed
|
||||
edge: Arc<Mutex<Edge>>,
|
||||
}
|
||||
|
||||
/// Represents a output port for a block
|
||||
pub struct Out<T>
|
||||
{
|
||||
stream: Option<StreamProducer<T>>,
|
||||
tag_stream: Option<StreamProducer<Tag>>,
|
||||
tag_stream: Option<StreamProducer<TagSlot>>,
|
||||
|
||||
// Will rarely be accessed
|
||||
edge: Arc<Mutex<Edge>>,
|
||||
}
|
||||
|
||||
pub struct InReader<'a, T>
|
||||
// Input Output interfaces
|
||||
|
||||
/// Output interface to write elements in a "push" fashion
|
||||
pub struct OutPush<'a, T>
|
||||
{
|
||||
data_reader: StreamReader<'a, T>,
|
||||
tag_reader: StreamReader<'a, Tag>,
|
||||
data_writer: ManuallyDrop<StreamWriter<'a, T>>,
|
||||
tag_writer: ManuallyDrop<StreamWriter<'a, TagSlot>>,
|
||||
|
||||
total_length: usize,
|
||||
written_data: usize,
|
||||
written_tags: usize,
|
||||
start_index: usize,
|
||||
}
|
||||
|
||||
pub struct OutWriter<'a, T>
|
||||
impl<'a, T: 'static> OutPush<'a, T>
|
||||
{
|
||||
data_writer: StreamWriter<'a, T>,
|
||||
tag_writer: StreamWriter<'a, Tag>,
|
||||
pub fn len(&self) -> usize
|
||||
{
|
||||
let data_slices = self.data_writer.slices();
|
||||
// This gives better performance !
|
||||
// Probably because it prevents claiming the whole buffer at once
|
||||
// But this is hacky. It should probably be managed at a lower level
|
||||
// ((data_slices.0.len() + data_slices.1.len()) / 2) - self.written_data
|
||||
|
||||
data_slices.0.len() + data_slices.1.len() - self.written_data
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool
|
||||
{
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn push(&mut self, data: Tagged<T>) -> Result<(), Tagged<T>>
|
||||
{
|
||||
// println!("\n\n\n");
|
||||
if self.written_data >= self.total_length
|
||||
{
|
||||
return Err(data);
|
||||
}
|
||||
|
||||
let data_slices = self.data_writer.slices_mut();
|
||||
let tag_slices = self.tag_writer.slices_mut();
|
||||
|
||||
// Write a data
|
||||
let data_ref = {
|
||||
if self.written_data < data_slices.0.len()
|
||||
{
|
||||
&mut data_slices.0[self.written_data]
|
||||
}
|
||||
else
|
||||
{
|
||||
&mut data_slices.1[self.written_data - data_slices.0.len()]
|
||||
}
|
||||
};
|
||||
// Index of the taken element within the stream.
|
||||
*data_ref = MaybeUninit::new(data.0);
|
||||
let element_index = self.start_index + self.written_data;
|
||||
self.written_data += 1;
|
||||
|
||||
// Check for corresponding tag
|
||||
let tag_ref = {
|
||||
if self.written_tags < tag_slices.0.len()
|
||||
{
|
||||
&mut tag_slices.0[self.written_tags]
|
||||
}
|
||||
else
|
||||
{
|
||||
&mut tag_slices.1[self.written_tags - tag_slices.0.len()]
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(tag) = data.1
|
||||
{
|
||||
*tag_ref = MaybeUninit::new(TagSlot {
|
||||
position: element_index,
|
||||
tag,
|
||||
});
|
||||
self.written_tags += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for OutPush<'a, T>
|
||||
{
|
||||
fn drop(&mut self)
|
||||
{
|
||||
let mut data_writer =
|
||||
unsafe { ManuallyDrop::<StreamWriter<'a, T>>::take(&mut self.data_writer) };
|
||||
let mut tag_writer =
|
||||
unsafe { ManuallyDrop::<StreamWriter<'a, TagSlot>>::take(&mut self.tag_writer) };
|
||||
|
||||
|
||||
tag_writer.produce(self.written_tags);
|
||||
data_writer.produce(self.written_data);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InIter<'a, T>
|
||||
{
|
||||
data_reader: ManuallyDrop<StreamReader<'a, T>>,
|
||||
tag_reader: ManuallyDrop<StreamReader<'a, TagSlot>>,
|
||||
|
||||
total_length: usize,
|
||||
total_tag_length: usize,
|
||||
read_data: usize,
|
||||
read_tags: usize,
|
||||
start_index: usize,
|
||||
}
|
||||
|
||||
impl<'a, T> Iterator for InIter<'a, T>
|
||||
{
|
||||
type Item = Tagged<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item>
|
||||
{
|
||||
if self.read_data >= self.total_length
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let data_slices = self.data_reader.slices_mut();
|
||||
let tag_slices = self.tag_reader.slices_mut();
|
||||
|
||||
// Take a data
|
||||
// SAFETY:
|
||||
// All takable should contain a valid element: guarteed by the queue
|
||||
// We strictly monotonicly take all elements, in order, in the slices.
|
||||
// No two same indices should be taken.
|
||||
// We cound the number of taken elemnts and consume the correct amount from the queue
|
||||
let data = {
|
||||
if self.read_data < data_slices.0.len()
|
||||
{
|
||||
unsafe { data_slices.0[self.read_data].take() }
|
||||
}
|
||||
else
|
||||
{
|
||||
unsafe { data_slices.1[self.read_data - data_slices.0.len()].take() }
|
||||
}
|
||||
};
|
||||
// Index of the taken element within the stream.
|
||||
let element_index = self.start_index + self.read_data;
|
||||
self.read_data += 1;
|
||||
|
||||
// Check for corresponding tag
|
||||
let mut tag = None;
|
||||
if self.read_tags < self.total_tag_length
|
||||
{
|
||||
let tag_ref = {
|
||||
if self.read_tags < tag_slices.0.len()
|
||||
{
|
||||
&mut tag_slices.0[self.read_tags]
|
||||
}
|
||||
else
|
||||
{
|
||||
&mut tag_slices.1[self.read_tags - tag_slices.0.len()]
|
||||
}
|
||||
};
|
||||
|
||||
// SAFETY:
|
||||
// Same as before : strictly monotic access in the tag slices
|
||||
if unsafe { tag_ref.peek().position == element_index }
|
||||
{
|
||||
// The next tag in line is tagging the just-poped element.
|
||||
// We get it
|
||||
tag = Some(unsafe { tag_ref.take().tag });
|
||||
self.read_tags += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Some(Tagged::new(data, tag))
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>)
|
||||
{
|
||||
let len =
|
||||
self.data_reader.slices().0.len() + self.data_reader.slices().1.len() - self.read_data;
|
||||
(len, Some(len))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for InIter<'a, T>
|
||||
{
|
||||
fn drop(&mut self)
|
||||
{
|
||||
let mut data_reader =
|
||||
unsafe { ManuallyDrop::<StreamReader<'a, T>>::take(&mut self.data_reader) };
|
||||
let mut tag_reader =
|
||||
unsafe { ManuallyDrop::<StreamReader<'a, TagSlot>>::take(&mut self.tag_reader) };
|
||||
tag_reader.consume(self.read_tags);
|
||||
data_reader.consume(self.read_data);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> ExactSizeIterator for InIter<'a, T> {}
|
||||
|
||||
impl<T: 'static> In<T>
|
||||
{
|
||||
pub fn iter<'a>(&'a mut self) -> InIter<'a, T>
|
||||
{
|
||||
let first_index = self.stream.as_ref().unwrap().first_index();
|
||||
let data_reader = self.stream.as_mut().unwrap().read_takable();
|
||||
let total_length = data_reader.slices().0.len() + data_reader.slices().1.len();
|
||||
let tag_reader = self.tag_stream.as_mut().unwrap().read_takable();
|
||||
let total_tag_length = tag_reader.slices().0.len() + tag_reader.slices().1.len();
|
||||
|
||||
InIter {
|
||||
data_reader: ManuallyDrop::new(data_reader),
|
||||
tag_reader: ManuallyDrop::new(tag_reader),
|
||||
read_data: 0,
|
||||
read_tags: 0,
|
||||
total_length,
|
||||
total_tag_length,
|
||||
start_index: first_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> Out<T>
|
||||
{
|
||||
pub fn write_push<'a>(&'a mut self) -> OutPush<'a, T>
|
||||
{
|
||||
let first_index = self.stream.as_ref().unwrap().first_index();
|
||||
let data_writer = self.stream.as_mut().unwrap().write();
|
||||
let total_length = data_writer.slices().0.len() + data_writer.slices().1.len();
|
||||
|
||||
let tag_writer = self.tag_stream.as_mut().unwrap().write();
|
||||
|
||||
OutPush {
|
||||
data_writer: ManuallyDrop::new(data_writer),
|
||||
tag_writer: ManuallyDrop::new(tag_writer),
|
||||
total_length,
|
||||
written_data: 0,
|
||||
written_tags: 0,
|
||||
start_index: first_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes an iterator to the output, sending the maximum amount of elements
|
||||
/// to the output.
|
||||
///
|
||||
/// It will not consume the iterator more than what can be sent.
|
||||
///
|
||||
/// ```
|
||||
/// let writer = output.write();
|
||||
///
|
||||
/// // Send only 42s to the output
|
||||
/// writer.push_iter(std::iter::repeat(42));
|
||||
/// ```
|
||||
pub fn push_iter<I: Iterator<Item = Tagged<T>>>(&mut self, mut iter: I) -> bool
|
||||
{
|
||||
let mut pusher = self.write_push();
|
||||
let mut len = pusher.len();
|
||||
|
||||
while len > 0
|
||||
{
|
||||
len -= 1;
|
||||
match iter.next()
|
||||
{
|
||||
Some(element) => {
|
||||
let _ = pusher.push(element);
|
||||
},
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Meta information
|
||||
/// Returns a string of the type of the output
|
||||
pub fn get_type_name(&self) -> &'static str
|
||||
{
|
||||
std::any::type_name::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a stream that can then be used to link blocks
|
||||
///
|
||||
/// ```rust
|
||||
/// let (output, input) = oxydsp-flowgraph::io::stream();
|
||||
///
|
||||
/// let writer = output.write();
|
||||
/// let reader = input.read();
|
||||
///
|
||||
/// // ...
|
||||
/// ```
|
||||
pub fn stream<T>() -> (Out<T>, In<T>)
|
||||
{
|
||||
let edge = Arc::new(Mutex::new(Edge::default()));
|
||||
@ -61,317 +339,97 @@ pub fn stream<T>() -> (Out<T>, In<T>)
|
||||
)
|
||||
}
|
||||
|
||||
impl<T: 'static> In<T>
|
||||
pub fn streams<T, const N: usize>() -> ([Out<T>; N], [In<T>; N])
|
||||
{
|
||||
pub fn set_block_index(&self, index: BlockIOIndex)
|
||||
{
|
||||
self.edge.lock().unwrap().to = Some(index);
|
||||
}
|
||||
// Ugly simultanous initialization
|
||||
let mut ins: [_; N] = std::array::from_fn(|_| None);
|
||||
let mut outs: [_; N] = std::array::from_fn(|_| None);
|
||||
|
||||
pub fn get_producer_block(&self) -> Option<BlockIOIndex>
|
||||
{
|
||||
self.edge.lock().unwrap().from
|
||||
}
|
||||
ins.iter_mut()
|
||||
.zip(outs.iter_mut())
|
||||
.for_each(|(input, output)| {
|
||||
let (newout, newin) = stream();
|
||||
*input = Some(newin);
|
||||
*output = Some(newout);
|
||||
});
|
||||
|
||||
pub fn set_anonymous_stream(&mut self, consumer: AnonymousStreamConsumer)
|
||||
{
|
||||
let (stream, tag_stream) = consumer.downcast::<T>();
|
||||
self.stream = Some(stream);
|
||||
self.tag_stream = Some(tag_stream);
|
||||
}
|
||||
let ins_some: [_; N] = std::array::from_fn(|i| ins[i].take().unwrap());
|
||||
let outs_some: [_; N] = std::array::from_fn(|i| outs[i].take().unwrap());
|
||||
|
||||
pub fn read<'a>(&'a mut self) -> InReader<'a, T>
|
||||
{
|
||||
let data_reader = self.stream.as_mut().unwrap().read();
|
||||
let tag_reader = self.tag_stream.as_mut().unwrap().read();
|
||||
InReader {
|
||||
data_reader,
|
||||
tag_reader,
|
||||
}
|
||||
}
|
||||
(outs_some, ins_some)
|
||||
}
|
||||
|
||||
impl<T: 'static> Out<T>
|
||||
{
|
||||
pub fn set_block_index(&self, index: BlockIOIndex)
|
||||
{
|
||||
self.edge.lock().unwrap().from = Some(index);
|
||||
}
|
||||
// --------------------
|
||||
// Iterator facilites
|
||||
// --------------------
|
||||
|
||||
pub fn get_consumer_block(&self) -> Option<BlockIOIndex>
|
||||
{
|
||||
self.edge.lock().unwrap().to
|
||||
}
|
||||
// An iterator type to push data to output(s)
|
||||
// pub struct PopIter<T>
|
||||
// {
|
||||
// len: usize,
|
||||
// popped: usize,
|
||||
// reader: T,
|
||||
// }
|
||||
|
||||
pub fn set_anonymous_stream(&mut self, producer: AnonymousStreamProducer)
|
||||
{
|
||||
let (stream, tag_stream) = producer.downcast::<T>();
|
||||
self.stream = Some(stream);
|
||||
self.tag_stream = Some(tag_stream);
|
||||
}
|
||||
// Type on which data can be popped from
|
||||
// pub trait PopIterable<'a>
|
||||
// {
|
||||
// type Output;
|
||||
//
|
||||
// /// Returns an iterator on the input elements :
|
||||
// ///
|
||||
// /// ```
|
||||
// /// (&mut input_a, &mut input_b, &mut input_c).pop_iter().for_each(|(a, b, c)| println!("Got {a}, {b} and {c} !"));
|
||||
// /// ```
|
||||
// fn pop_iter(&'a mut self) -> PopIter<Self::Output>;
|
||||
// }
|
||||
|
||||
// Delegate stream creation to Out object
|
||||
// which knows the stream type
|
||||
pub fn create_anonymous_stream(
|
||||
&self,
|
||||
capacity: usize,
|
||||
) -> (AnonymousStreamProducer, AnonymousStreamConsumer)
|
||||
{
|
||||
let (tx, rx) = stream::bounded_queue::<T>(capacity);
|
||||
let (tx_tag, rx_tag) = stream::bounded_queue::<Tag>(capacity);
|
||||
((tx, tx_tag).into(), (rx, rx_tag).into())
|
||||
}
|
||||
// impl<'a, T: 'static> PopIterable<'a> for In<T>
|
||||
// {
|
||||
// type Output = InReader<'a, T>;
|
||||
// fn pop_iter(&'a mut self) -> PopIter<InReader<'a, T>>
|
||||
// {
|
||||
// let reader = self.read();
|
||||
// PopIter {
|
||||
// popped: 0,
|
||||
// len: reader.len(),
|
||||
// reader,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// generate_pop_iterable_tuple_impl! {1}
|
||||
// generate_pop_iterable_tuple_impl! {2}
|
||||
// generate_pop_iterable_tuple_impl! {3}
|
||||
// generate_pop_iterable_tuple_impl! {4}
|
||||
// generate_pop_iterable_tuple_impl! {5}
|
||||
// generate_pop_iterable_tuple_impl! {6}
|
||||
// generate_pop_iterable_tuple_impl! {7}
|
||||
// generate_pop_iterable_tuple_impl! {8}
|
||||
// generate_pop_iterable_tuple_impl! {9}
|
||||
// generate_pop_iterable_tuple_impl! {10}
|
||||
// generate_pop_iterable_tuple_impl! {11}
|
||||
// generate_pop_iterable_tuple_impl! {12}
|
||||
//
|
||||
// impl<'a, T> Iterator for PopIter<InReader<'a, T>>
|
||||
// {
|
||||
// type Item = Tagged<T>;
|
||||
//
|
||||
// fn next(&mut self) -> Option<Self::Item>
|
||||
// {
|
||||
// self.reader.pop()
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn write<'a>(&'a mut self) -> OutWriter<'a, T>
|
||||
{
|
||||
OutWriter {
|
||||
data_writer: self.stream.as_mut().unwrap().write(),
|
||||
tag_writer: self.tag_stream.as_mut().unwrap().write(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_iter<I: Iterator<Item = (T, Option<Tag>)>>(&mut self, mut iter: I) -> bool
|
||||
{
|
||||
let writer = self.write();
|
||||
let len = writer.len();
|
||||
|
||||
for _ in 0..len
|
||||
{
|
||||
if let Some(elt) = iter.next()
|
||||
{
|
||||
match elt.1
|
||||
{
|
||||
Some(tag) =>
|
||||
{
|
||||
let _ = writer.push_tagged(elt.0, tag);
|
||||
}
|
||||
None =>
|
||||
{
|
||||
let _ = writer.push_no_tag(elt.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// Meta information
|
||||
pub fn get_type_name(&self) -> &'static str
|
||||
{
|
||||
std::any::type_name::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> InReader<'_, T>
|
||||
{
|
||||
pub fn len(&self) -> usize
|
||||
{
|
||||
self.data_reader.len()
|
||||
}
|
||||
|
||||
pub fn pop_tagged(&self) -> Option<(T, Option<Tag>)>
|
||||
{
|
||||
let data = self.data_reader.pop_with_index();
|
||||
if let Some((data, index)) = data
|
||||
{
|
||||
let mut tag = None;
|
||||
if self
|
||||
.tag_reader
|
||||
.peek(|t| t.position)
|
||||
.is_some_and(|x| x == index)
|
||||
{
|
||||
tag = self.tag_reader.pop();
|
||||
}
|
||||
Some((data, tag))
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop_drop_tag(&self) -> Option<T>
|
||||
{
|
||||
self.pop_tagged().map(|(data, _)| data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> OutWriter<'_, T>
|
||||
{
|
||||
pub fn len(&self) -> usize
|
||||
{
|
||||
self.data_writer.len().min(self.tag_writer.len())
|
||||
}
|
||||
|
||||
pub fn push(&self, (data, tag): (T, Option<Tag>)) -> Result<(), (T, Option<Tag>)>
|
||||
{
|
||||
match self.data_writer.push(data)
|
||||
{
|
||||
Ok(_) if tag.is_some() =>
|
||||
{
|
||||
let _ = self.tag_writer.push(tag.unwrap());
|
||||
Ok(())
|
||||
}
|
||||
Ok(_) => Ok(()),
|
||||
Err(data) => Err((data, tag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_tagged(&self, data: T, tag: Tag) -> Result<(), (T, Tag)>
|
||||
{
|
||||
let res = self.data_writer.push(data);
|
||||
match res
|
||||
{
|
||||
Ok(_) =>
|
||||
{
|
||||
let _ = self.tag_writer.push(tag);
|
||||
Ok(())
|
||||
}
|
||||
Err(t) => Err((t, tag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_no_tag(&self, data: T) -> Result<(), T>
|
||||
{
|
||||
self.data_writer.push(data)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PopIter<T>
|
||||
{
|
||||
len: usize,
|
||||
popped: usize,
|
||||
reader: T,
|
||||
}
|
||||
|
||||
pub trait PopIterable<'a>
|
||||
{
|
||||
type Output;
|
||||
fn pop_iter(&'a mut self) -> PopIter<Self::Output>;
|
||||
}
|
||||
|
||||
impl<'a, T: 'static> PopIterable<'a> for In<T>
|
||||
{
|
||||
type Output = InReader<'a, T>;
|
||||
fn pop_iter(&'a mut self) -> PopIter<InReader<'a, T>>
|
||||
{
|
||||
let reader = self.read();
|
||||
PopIter {
|
||||
popped: 0,
|
||||
len: reader.len(),
|
||||
reader,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generate_pop_iterable_tuple_impl! {2}
|
||||
generate_pop_iterable_tuple_impl! {3}
|
||||
generate_pop_iterable_tuple_impl! {4}
|
||||
generate_pop_iterable_tuple_impl! {5}
|
||||
generate_pop_iterable_tuple_impl! {6}
|
||||
generate_pop_iterable_tuple_impl! {7}
|
||||
generate_pop_iterable_tuple_impl! {8}
|
||||
generate_pop_iterable_tuple_impl! {9}
|
||||
generate_pop_iterable_tuple_impl! {10}
|
||||
generate_pop_iterable_tuple_impl! {11}
|
||||
generate_pop_iterable_tuple_impl! {12}
|
||||
generate_pop_iterable_tuple_impl! {13}
|
||||
generate_pop_iterable_tuple_impl! {14}
|
||||
generate_pop_iterable_tuple_impl! {15}
|
||||
generate_pop_iterable_tuple_impl! {16}
|
||||
generate_pop_iterable_tuple_impl! {17}
|
||||
generate_pop_iterable_tuple_impl! {18}
|
||||
generate_pop_iterable_tuple_impl! {19}
|
||||
generate_pop_iterable_tuple_impl! {20}
|
||||
|
||||
impl<'a, T> Iterator for PopIter<InReader<'a, T>>
|
||||
{
|
||||
type Item = (T, Option<Tag>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item>
|
||||
{
|
||||
self.reader.pop_tagged()
|
||||
}
|
||||
}
|
||||
|
||||
impl_iterator_for_pop_iter_tuple! {2}
|
||||
impl_iterator_for_pop_iter_tuple! {3}
|
||||
impl_iterator_for_pop_iter_tuple! {4}
|
||||
impl_iterator_for_pop_iter_tuple! {5}
|
||||
impl_iterator_for_pop_iter_tuple! {6}
|
||||
impl_iterator_for_pop_iter_tuple! {7}
|
||||
impl_iterator_for_pop_iter_tuple! {8}
|
||||
impl_iterator_for_pop_iter_tuple! {9}
|
||||
impl_iterator_for_pop_iter_tuple! {10}
|
||||
impl_iterator_for_pop_iter_tuple! {11}
|
||||
impl_iterator_for_pop_iter_tuple! {12}
|
||||
impl_iterator_for_pop_iter_tuple! {13}
|
||||
impl_iterator_for_pop_iter_tuple! {14}
|
||||
impl_iterator_for_pop_iter_tuple! {15}
|
||||
impl_iterator_for_pop_iter_tuple! {16}
|
||||
impl_iterator_for_pop_iter_tuple! {17}
|
||||
impl_iterator_for_pop_iter_tuple! {18}
|
||||
impl_iterator_for_pop_iter_tuple! {19}
|
||||
impl_iterator_for_pop_iter_tuple! {20}
|
||||
|
||||
// Needed for graph to be able to manipulate
|
||||
// stream endings without knowing the generic type
|
||||
pub struct AnonymousStreamProducer
|
||||
{
|
||||
inner: Box<dyn Any>,
|
||||
inner_tag: StreamProducer<Tag>,
|
||||
}
|
||||
|
||||
pub struct AnonymousStreamConsumer
|
||||
{
|
||||
inner: Box<dyn Any>,
|
||||
inner_tag: StreamConsumer<Tag>,
|
||||
}
|
||||
|
||||
impl<T: 'static> From<(StreamProducer<T>, StreamProducer<Tag>)> for AnonymousStreamProducer
|
||||
{
|
||||
fn from(value: (StreamProducer<T>, StreamProducer<Tag>)) -> Self
|
||||
{
|
||||
AnonymousStreamProducer {
|
||||
inner: Box::new(value.0),
|
||||
inner_tag: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> From<(StreamConsumer<T>, StreamConsumer<Tag>)> for AnonymousStreamConsumer
|
||||
{
|
||||
fn from(value: (StreamConsumer<T>, StreamConsumer<Tag>)) -> Self
|
||||
{
|
||||
AnonymousStreamConsumer {
|
||||
inner: Box::new(value.0),
|
||||
inner_tag: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnonymousStreamProducer
|
||||
{
|
||||
pub fn downcast<T: 'static>(self) -> (StreamProducer<T>, StreamProducer<Tag>)
|
||||
{
|
||||
(
|
||||
*self.inner.downcast::<StreamProducer<T>>().unwrap(),
|
||||
self.inner_tag,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnonymousStreamConsumer
|
||||
{
|
||||
pub fn downcast<T: 'static>(self) -> (StreamConsumer<T>, StreamConsumer<Tag>)
|
||||
{
|
||||
(
|
||||
*self.inner.downcast::<StreamConsumer<T>>().unwrap(),
|
||||
self.inner_tag,
|
||||
)
|
||||
}
|
||||
}
|
||||
// impl_iterator_for_pop_iter_tuple! {1}
|
||||
// impl_iterator_for_pop_iter_tuple! {2}
|
||||
// impl_iterator_for_pop_iter_tuple! {3}
|
||||
// impl_iterator_for_pop_iter_tuple! {4}
|
||||
// impl_iterator_for_pop_iter_tuple! {5}
|
||||
// impl_iterator_for_pop_iter_tuple! {6}
|
||||
// impl_iterator_for_pop_iter_tuple! {7}
|
||||
// impl_iterator_for_pop_iter_tuple! {8}
|
||||
// impl_iterator_for_pop_iter_tuple! {9}
|
||||
// impl_iterator_for_pop_iter_tuple! {10}
|
||||
// impl_iterator_for_pop_iter_tuple! {11}
|
||||
// impl_iterator_for_pop_iter_tuple! {12}
|
||||
|
||||
182
oxydsp-flowgraph/src/io/edge.rs
Normal file
182
oxydsp-flowgraph/src/io/edge.rs
Normal file
@ -0,0 +1,182 @@
|
||||
use std::any::Any;
|
||||
|
||||
use crate::{io::{In, Out}, stream::{self, StreamConsumer, StreamProducer}, tag::TagSlot};
|
||||
|
||||
/// Shared object between a block's input and output objects
|
||||
/// so they can "communicate" and know about each other
|
||||
#[derive(Default)]
|
||||
pub struct Edge
|
||||
{
|
||||
/// Represents the index of the block owning the Out end in the graph
|
||||
/// And the the output index within that block
|
||||
pub from: Option<BlockIOIndex>,
|
||||
|
||||
/// Represents the index of the block owning the In end in the graph
|
||||
/// And the the input index within that block
|
||||
pub to: Option<BlockIOIndex>,
|
||||
}
|
||||
|
||||
/// Reprensents the location of a port (input or output) in terms of
|
||||
/// - The block index in which they exist
|
||||
/// - Their input or output index within that block
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct BlockIOIndex
|
||||
{
|
||||
pub block_index: usize,
|
||||
pub port_index: usize,
|
||||
}
|
||||
|
||||
/// Trait to manipulate a block's input in a type agnostic/erased way
|
||||
pub trait AnonymousIn
|
||||
{
|
||||
/// Inform the input about the index of the blocks it's in, as well as its port index
|
||||
fn set_index(&self, index: BlockIOIndex);
|
||||
|
||||
/// Returns None or the block index of the block, and the block port of the corresponding
|
||||
/// Out object
|
||||
fn get_producer_block(&self) -> Option<BlockIOIndex>;
|
||||
|
||||
/// Sets the internal stream object
|
||||
fn set_anonymous_stream(&mut self, consumer: AnonymousStreamConsumer);
|
||||
}
|
||||
|
||||
/// Trait to manipulate a block's output in a type agnostic/erased way
|
||||
pub trait AnonymousOut
|
||||
{
|
||||
/// Inform the output about the index of the blocks it's in, as well as its port index
|
||||
fn set_index(&self, index: BlockIOIndex);
|
||||
|
||||
/// Sets the internal stream object
|
||||
fn set_anonymous_stream(&mut self, producer: AnonymousStreamProducer);
|
||||
|
||||
/// Returns None or the block index of the block, and the block port of the corresponding
|
||||
/// In object
|
||||
fn get_consumer_block(&self) -> Option<BlockIOIndex>;
|
||||
|
||||
/// Creates the stream with the correct corresponding type, in a type erased way.
|
||||
///
|
||||
/// This delegation of stream creation is necessary to allow the graph to manipulate
|
||||
/// it, as it cannot know about the generic type of the stream.
|
||||
fn create_anonymous_stream(
|
||||
&self,
|
||||
capacity: usize,
|
||||
) -> (AnonymousStreamProducer, AnonymousStreamConsumer);
|
||||
}
|
||||
|
||||
impl<T: 'static> AnonymousIn for In<T>
|
||||
{
|
||||
fn set_index(&self, index: BlockIOIndex)
|
||||
{
|
||||
self.edge.lock().unwrap().to = Some(index);
|
||||
}
|
||||
|
||||
fn get_producer_block(&self) -> Option<BlockIOIndex>
|
||||
{
|
||||
self.edge.lock().unwrap().from
|
||||
}
|
||||
|
||||
fn set_anonymous_stream(&mut self, consumer: AnonymousStreamConsumer)
|
||||
{
|
||||
let (stream, tag_stream) = consumer.downcast::<T>();
|
||||
self.stream = Some(stream);
|
||||
self.tag_stream = Some(tag_stream);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> AnonymousOut for Out<T>
|
||||
{
|
||||
fn set_index(&self, index: BlockIOIndex)
|
||||
{
|
||||
self.edge.lock().unwrap().from = Some(index);
|
||||
}
|
||||
|
||||
fn get_consumer_block(&self) -> Option<BlockIOIndex>
|
||||
{
|
||||
self.edge.lock().unwrap().to
|
||||
}
|
||||
|
||||
fn set_anonymous_stream(&mut self, producer: AnonymousStreamProducer)
|
||||
{
|
||||
let (stream, tag_stream) = producer.downcast::<T>();
|
||||
self.stream = Some(stream);
|
||||
self.tag_stream = Some(tag_stream);
|
||||
}
|
||||
|
||||
// Delegate stream creation to Out object
|
||||
// which knows the stream type
|
||||
fn create_anonymous_stream(
|
||||
&self,
|
||||
capacity: usize,
|
||||
) -> (AnonymousStreamProducer, AnonymousStreamConsumer)
|
||||
{
|
||||
let (tx, rx) = stream::bounded_queue::<T>(capacity);
|
||||
let (tx_tag, rx_tag) = stream::bounded_queue::<TagSlot>(capacity);
|
||||
((tx, tx_tag).into(), (rx, rx_tag).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// StreamProducer object for data and tags stored in a type
|
||||
/// agnostic/erased way.
|
||||
///
|
||||
/// This is needed for the graph system to manipulate and pass arround these objects
|
||||
/// as they can't/don't know about the generic types of the stream objects
|
||||
pub struct AnonymousStreamProducer
|
||||
{
|
||||
inner: Box<dyn Any>,
|
||||
inner_tag: StreamProducer<TagSlot>,
|
||||
}
|
||||
|
||||
/// StreamConsumer object for data and tags stored in a type
|
||||
/// agnostic/erased way.
|
||||
///
|
||||
/// This is needed for the graph system to manipulate and pass arround these objects
|
||||
/// as they can't/don't know about the generic types of the stream objects
|
||||
pub struct AnonymousStreamConsumer
|
||||
{
|
||||
inner: Box<dyn Any>,
|
||||
inner_tag: StreamConsumer<TagSlot>,
|
||||
}
|
||||
|
||||
impl<T: 'static> From<(StreamProducer<T>, StreamProducer<TagSlot>)> for AnonymousStreamProducer
|
||||
{
|
||||
fn from(value: (StreamProducer<T>, StreamProducer<TagSlot>)) -> Self
|
||||
{
|
||||
AnonymousStreamProducer {
|
||||
inner: Box::new(value.0),
|
||||
inner_tag: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> From<(StreamConsumer<T>, StreamConsumer<TagSlot>)> for AnonymousStreamConsumer
|
||||
{
|
||||
fn from(value: (StreamConsumer<T>, StreamConsumer<TagSlot>)) -> Self
|
||||
{
|
||||
AnonymousStreamConsumer {
|
||||
inner: Box::new(value.0),
|
||||
inner_tag: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnonymousStreamProducer
|
||||
{
|
||||
pub(crate) fn downcast<T: 'static>(self) -> (StreamProducer<T>, StreamProducer<TagSlot>)
|
||||
{
|
||||
(
|
||||
*self.inner.downcast::<StreamProducer<T>>().unwrap(),
|
||||
self.inner_tag,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnonymousStreamConsumer
|
||||
{
|
||||
pub(crate) fn downcast<T: 'static>(self) -> (StreamConsumer<T>, StreamConsumer<TagSlot>)
|
||||
{
|
||||
(
|
||||
*self.inner.downcast::<StreamConsumer<T>>().unwrap(),
|
||||
self.inner_tag,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,9 @@
|
||||
// This crate manages the flowgraph datastructures and execution/scheduling
|
||||
// as well as the communication between the blocks
|
||||
|
||||
/// This crate manages the flowgraph datastructures and execution/scheduling
|
||||
/// as well as the communication between the blocks
|
||||
pub mod block;
|
||||
pub mod edge;
|
||||
pub mod graph;
|
||||
pub mod io;
|
||||
pub mod stream;
|
||||
pub mod tag;
|
||||
pub use oxydsp_flowgraph_macros::{BlockIO, sync_block};
|
||||
pub use oxydsp_flowgraph_macros::BlockIO;
|
||||
pub use oxydsp_flowgraph_macros::sync_block;
|
||||
|
||||
@ -1 +0,0 @@
|
||||
fn main() {}
|
||||
@ -1,6 +1,4 @@
|
||||
use std::cell::Cell;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::io::empty;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
@ -54,36 +52,35 @@ unsafe impl<T: Send> Sync for StreamProducer<T> {}
|
||||
unsafe impl<T: Send> Send for StreamConsumer<T> {}
|
||||
unsafe impl<T: Send> Sync for StreamConsumer<T> {}
|
||||
|
||||
// Represents a write operation within a stream producer
|
||||
pub struct StreamWriter<'a, T>
|
||||
{
|
||||
producer: &'a mut StreamProducer<T>,
|
||||
first: &'a UnsafeCell<[MaybeUninit<T>]>,
|
||||
second: Option<&'a UnsafeCell<[MaybeUninit<T>]>>,
|
||||
first_len: usize,
|
||||
second_len: usize,
|
||||
written: Cell<usize>,
|
||||
#[repr(transparent)]
|
||||
pub struct Takable<T>(MaybeUninit<T>);
|
||||
|
||||
// Index of the first element to be pushed
|
||||
// within the "infinite buffer"
|
||||
// Used to number tags
|
||||
start_index: usize,
|
||||
impl<T> Takable<T>
|
||||
{
|
||||
pub fn new(element: T) -> Self
|
||||
{
|
||||
Takable(MaybeUninit::new(element))
|
||||
}
|
||||
|
||||
pub unsafe fn take(&mut self) -> T
|
||||
{
|
||||
unsafe { std::mem::replace(&mut self.0, MaybeUninit::uninit()).assume_init() }
|
||||
}
|
||||
|
||||
pub unsafe fn peek(&self) -> &T
|
||||
{
|
||||
unsafe { self.0.assume_init_ref() }
|
||||
}
|
||||
|
||||
pub unsafe fn peek_mut(&mut self) -> &mut T
|
||||
{
|
||||
unsafe { self.0.assume_init_mut() }
|
||||
}
|
||||
}
|
||||
|
||||
// Represents a read operation within a stream producer
|
||||
pub struct StreamReader<'a, T>
|
||||
unsafe fn takable_slice_from_maybe_uninitt<T>(slice: &mut [MaybeUninit<T>]) -> &mut [Takable<T>]
|
||||
{
|
||||
producer: &'a StreamConsumer<T>,
|
||||
first: &'a UnsafeCell<[MaybeUninit<T>]>,
|
||||
second: Option<&'a UnsafeCell<[MaybeUninit<T>]>>,
|
||||
first_len: usize,
|
||||
second_len: usize,
|
||||
read: Cell<usize>,
|
||||
|
||||
// Index of the first element to be read
|
||||
// within the "infinite buffer"
|
||||
// Used to number tags
|
||||
start_index: usize,
|
||||
unsafe { std::mem::transmute(slice) }
|
||||
}
|
||||
|
||||
pub fn bounded_queue<T>(capacity: usize) -> (StreamProducer<T>, StreamConsumer<T>)
|
||||
@ -122,8 +119,78 @@ pub fn bounded_queue<T>(capacity: usize) -> (StreamProducer<T>, StreamConsumer<T
|
||||
)
|
||||
}
|
||||
|
||||
pub struct StreamReader<'a, T>
|
||||
{
|
||||
slices: (&'a mut [Takable<T>], &'a mut [Takable<T>]),
|
||||
|
||||
// UNSAFE !
|
||||
inner: &'a mut StreamConsumer<T>,
|
||||
}
|
||||
|
||||
pub struct StreamWriter<'a, T>
|
||||
{
|
||||
slices: (&'a mut [MaybeUninit<T>], &'a mut [MaybeUninit<T>]),
|
||||
|
||||
// UNSAFE !
|
||||
inner: &'a mut StreamProducer<T>,
|
||||
}
|
||||
|
||||
impl<'a, T> StreamReader<'a, T>
|
||||
{
|
||||
pub fn slices(&self) -> (&[Takable<T>], &[Takable<T>])
|
||||
{
|
||||
(self.slices.0, self.slices.1)
|
||||
}
|
||||
|
||||
pub fn slices_mut(&mut self) -> (&mut [Takable<T>], &mut [Takable<T>])
|
||||
{
|
||||
(self.slices.0, self.slices.1)
|
||||
}
|
||||
|
||||
pub fn consume(&mut self, read: usize)
|
||||
{
|
||||
self.inner.consume(read);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> StreamWriter<'a, T>
|
||||
{
|
||||
pub fn slices(&self) -> (&[MaybeUninit<T>], &[MaybeUninit<T>])
|
||||
{
|
||||
(self.slices.0, self.slices.1)
|
||||
}
|
||||
|
||||
pub fn slices_mut(&mut self) -> (&mut [MaybeUninit<T>], &mut [MaybeUninit<T>])
|
||||
{
|
||||
(self.slices.0, self.slices.1)
|
||||
}
|
||||
|
||||
pub fn produce(&mut self, written: usize)
|
||||
{
|
||||
self.inner.produce(written);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StreamProducer<T>
|
||||
{
|
||||
pub fn first_index(&self) -> usize
|
||||
{
|
||||
self.inner.head.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn produce(&mut self, written: usize)
|
||||
{
|
||||
// Advance head.
|
||||
let head = self.inner.head.load(Ordering::Relaxed);
|
||||
let tail = self.inner.tail.load(Ordering::Relaxed);
|
||||
|
||||
// Check bounds
|
||||
assert!(head + written - tail <= (self.inner.capacity_mask + 1));
|
||||
|
||||
// We want writes to the buffer to be visible when acquired in the pop side
|
||||
self.inner.head.store(head + written, Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn write<'a>(&'a mut self) -> StreamWriter<'a, T>
|
||||
{
|
||||
// We need to claim the maximum amount of elements.
|
||||
@ -157,57 +224,26 @@ impl<T> StreamProducer<T>
|
||||
let k = &mut *self.inner.buffer.get();
|
||||
|
||||
let (start_to_head, head_to_end) = k.split_at_mut_unchecked(wrapped_head);
|
||||
let (start_to_tail, _tail_to_head) =
|
||||
start_to_head.split_at_mut_unchecked(wrapped_tail);
|
||||
|
||||
// Slices are wrapped into unsafe cells to provide interior mutability
|
||||
// On the stream as it is much more convienient.
|
||||
//
|
||||
// SAFETY:
|
||||
//
|
||||
// This functions borrows the stream mutably. As such, only one instance
|
||||
// of StreamWriter can exist for a given stream. The StreamWriter
|
||||
// is thus the only on able to write or read the stream when it lives
|
||||
let first_len = head_to_end.len();
|
||||
let second_len = start_to_tail.len();
|
||||
let first = std::mem::transmute::<
|
||||
&mut [MaybeUninit<T>],
|
||||
&UnsafeCell<[MaybeUninit<T>]>,
|
||||
>(head_to_end);
|
||||
let second = Some(std::mem::transmute::<
|
||||
&mut [MaybeUninit<T>],
|
||||
&UnsafeCell<[MaybeUninit<T>]>,
|
||||
>(start_to_tail));
|
||||
// of these slices can exist for a given stream.
|
||||
StreamWriter {
|
||||
start_index: head,
|
||||
|
||||
producer: self,
|
||||
first,
|
||||
second,
|
||||
first_len,
|
||||
second_len,
|
||||
written: 0.into(),
|
||||
slices: (head_to_end, start_to_head),
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We MUST have : tail < head
|
||||
|
||||
if wrapped_tail < wrapped_head
|
||||
{
|
||||
//
|
||||
// Or
|
||||
// ▯▯▯▯▯▯▯▯▯▯▯▯▯
|
||||
// |
|
||||
// tail & head
|
||||
// (empty)
|
||||
// Current configuration :
|
||||
// ▯▯▯▮▮▮▮▮▮▯▯▯▯
|
||||
// | |
|
||||
// tail head
|
||||
// ___ ____
|
||||
// slice1 slice2
|
||||
// slice2 slice1
|
||||
|
||||
// SAFETY:
|
||||
//
|
||||
@ -224,27 +260,9 @@ impl<T> StreamProducer<T>
|
||||
let (start_to_tail, _tail_to_head) =
|
||||
start_to_head.split_at_mut_unchecked(wrapped_tail);
|
||||
|
||||
let first_len = head_to_end.len();
|
||||
let second_len = start_to_tail.len();
|
||||
|
||||
let first = std::mem::transmute::<
|
||||
&mut [MaybeUninit<T>],
|
||||
&UnsafeCell<[std::mem::MaybeUninit<T>]>,
|
||||
>(head_to_end);
|
||||
let second = Some(std::mem::transmute::<
|
||||
&mut [MaybeUninit<T>],
|
||||
&UnsafeCell<[MaybeUninit<T>]>,
|
||||
>(start_to_tail));
|
||||
|
||||
StreamWriter {
|
||||
start_index: head,
|
||||
|
||||
producer: self,
|
||||
first,
|
||||
second,
|
||||
first_len,
|
||||
second_len,
|
||||
written: 0.into(),
|
||||
slices: (head_to_end, start_to_tail),
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -262,8 +280,8 @@ impl<T> StreamProducer<T>
|
||||
// |
|
||||
// tail & head
|
||||
// (full)
|
||||
// ______.______
|
||||
// slice2 slice1
|
||||
// .
|
||||
// slice1
|
||||
|
||||
// SAFETY:
|
||||
//
|
||||
@ -273,19 +291,14 @@ impl<T> StreamProducer<T>
|
||||
// Head and tail are both indices of the slice
|
||||
unsafe {
|
||||
let k = &mut *self.inner.buffer.get();
|
||||
let len = wrapped_tail - wrapped_head;
|
||||
StreamWriter {
|
||||
start_index: head,
|
||||
let (start_to_tail, _tail_to_end) = k.split_at_mut_unchecked(wrapped_tail);
|
||||
let (_start_to_head, head_to_tail) =
|
||||
start_to_tail.split_at_mut_unchecked(wrapped_head);
|
||||
let (empty_slice, head_to_tail) = head_to_tail.split_at_mut_unchecked(0);
|
||||
|
||||
producer: self,
|
||||
first_len: len,
|
||||
second_len: 0,
|
||||
first: std::mem::transmute::<
|
||||
&[MaybeUninit<T>],
|
||||
&UnsafeCell<[MaybeUninit<T>]>,
|
||||
>(&k[wrapped_head..wrapped_tail]),
|
||||
second: None,
|
||||
written: 0.into(),
|
||||
StreamWriter {
|
||||
slices: (head_to_tail, empty_slice),
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -295,7 +308,25 @@ impl<T> StreamProducer<T>
|
||||
|
||||
impl<T> StreamConsumer<T>
|
||||
{
|
||||
pub fn read<'a>(&'a mut self) -> StreamReader<'a, T>
|
||||
pub fn consume(&mut self, read: usize)
|
||||
{
|
||||
// Advance head.
|
||||
let head = self.inner.head.load(Ordering::Relaxed);
|
||||
let tail = self.inner.tail.load(Ordering::Relaxed);
|
||||
|
||||
// Check bounds
|
||||
assert!(tail + read <= head);
|
||||
|
||||
// We want writes to the buffer to be visible when acquired in the pop side
|
||||
self.inner.tail.store(tail + read, Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn first_index(&self) -> usize
|
||||
{
|
||||
self.inner.tail.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn read_takable<'a>(&'a mut self) -> StreamReader<'a, T>
|
||||
{
|
||||
// We need to claim the maximum amount of elements.
|
||||
let head = self.inner.head.load(Ordering::Acquire);
|
||||
@ -312,24 +343,20 @@ impl<T> StreamConsumer<T>
|
||||
// Buffer is empty. Return empty slice
|
||||
unsafe {
|
||||
let k = &mut *self.inner.buffer.get();
|
||||
let len = wrapped_head - wrapped_tail;
|
||||
let empty = &mut k[0..0];
|
||||
let (empty_1, empty_2) = empty.split_at_mut_unchecked(0);
|
||||
StreamReader {
|
||||
start_index: tail,
|
||||
|
||||
producer: self,
|
||||
first_len: len,
|
||||
second_len: 0,
|
||||
first: std::mem::transmute::<&[MaybeUninit<T>], &UnsafeCell<[MaybeUninit<T>]>>(
|
||||
&k[wrapped_tail..wrapped_head],
|
||||
slices: (
|
||||
takable_slice_from_maybe_uninitt(empty_1),
|
||||
takable_slice_from_maybe_uninitt(empty_2),
|
||||
),
|
||||
second: None,
|
||||
read: 0.into(),
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Necessarly: wrapped_tail < wrapped_head
|
||||
// Necessarly: wrapped_tail <= wrapped_head
|
||||
// Two cases : The buffer overlaps the wrapping or not
|
||||
if wrapped_tail < wrapped_head
|
||||
{
|
||||
@ -347,20 +374,17 @@ impl<T> StreamConsumer<T>
|
||||
//
|
||||
// Head and tail are both indices of the slice
|
||||
unsafe {
|
||||
let k = &mut *self.inner.buffer.get();
|
||||
let len = wrapped_head - wrapped_tail;
|
||||
StreamReader {
|
||||
start_index: tail,
|
||||
let k = &mut (&mut *self.inner.buffer.get())[wrapped_tail..wrapped_head];
|
||||
let (tail_to_head, empty_slice) =
|
||||
k.split_at_mut_unchecked(wrapped_head - wrapped_tail);
|
||||
assert_eq!(empty_slice.len(), 0);
|
||||
|
||||
producer: self,
|
||||
first_len: len,
|
||||
second_len: 0,
|
||||
first: std::mem::transmute::<
|
||||
&[MaybeUninit<T>],
|
||||
&UnsafeCell<[MaybeUninit<T>]>,
|
||||
>(&k[wrapped_tail..wrapped_head]),
|
||||
second: None,
|
||||
read: 0.into(),
|
||||
StreamReader {
|
||||
slices: (
|
||||
takable_slice_from_maybe_uninitt(tail_to_head),
|
||||
takable_slice_from_maybe_uninitt(empty_slice),
|
||||
),
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -396,293 +420,33 @@ impl<T> StreamConsumer<T>
|
||||
let (start_to_head, _head_to_tail) =
|
||||
start_to_tail.split_at_mut_unchecked(wrapped_head);
|
||||
|
||||
let first_len = tail_to_end.len();
|
||||
let second_len = start_to_head.len();
|
||||
|
||||
let first = std::mem::transmute::<
|
||||
&mut [MaybeUninit<T>],
|
||||
&UnsafeCell<[MaybeUninit<T>]>,
|
||||
>(tail_to_end);
|
||||
let second = Some(std::mem::transmute::<
|
||||
&mut [MaybeUninit<T>],
|
||||
&UnsafeCell<[MaybeUninit<T>]>,
|
||||
>(start_to_head));
|
||||
|
||||
StreamReader {
|
||||
start_index: tail,
|
||||
|
||||
producer: self,
|
||||
first,
|
||||
second,
|
||||
first_len,
|
||||
second_len,
|
||||
read: 0.into(),
|
||||
slices: (
|
||||
takable_slice_from_maybe_uninitt(tail_to_end),
|
||||
takable_slice_from_maybe_uninitt(start_to_head),
|
||||
),
|
||||
inner: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a reader of contiguous elements that
|
||||
/// satisfy the predicate
|
||||
pub fn read_while<F>(&mut self, predicate: F) -> StreamReader<'_, T>
|
||||
where
|
||||
F: Fn(&T) -> bool,
|
||||
{
|
||||
// Take a normal reader. This contains available elements to read.
|
||||
let mut reader = self.read();
|
||||
|
||||
// We need to trim the slices to keep only the satified elements
|
||||
|
||||
// First slice
|
||||
let mut first_kept = 0;
|
||||
// SAFETY:
|
||||
//
|
||||
// Only us can have a reference to these slices of the buffer
|
||||
for element in unsafe { &*reader.first.get() }
|
||||
{
|
||||
// SAFETY
|
||||
//
|
||||
// If this element is in a reader returned by self.read
|
||||
// with no pop called, we know it is initialized
|
||||
let init_element = unsafe { element.assume_init_ref() };
|
||||
let sat = predicate(init_element);
|
||||
if !sat
|
||||
{
|
||||
// Stop here
|
||||
// Forget about second slice
|
||||
reader.second_len = 0;
|
||||
reader.second = None;
|
||||
|
||||
// Trim first slice
|
||||
reader.first_len = first_kept;
|
||||
unsafe {
|
||||
reader.first = std::mem::transmute::<
|
||||
&[MaybeUninit<T>],
|
||||
&UnsafeCell<[MaybeUninit<T>]>,
|
||||
>(&(&*reader.first.get())[0..first_kept]);
|
||||
}
|
||||
|
||||
return reader;
|
||||
}
|
||||
first_kept += 1;
|
||||
}
|
||||
|
||||
// If we are here, all of the elements of the first slice, satisfy the predicate
|
||||
|
||||
if let Some(second_slice) = &mut reader.second
|
||||
{
|
||||
// Second slice
|
||||
let mut second_kept = 0;
|
||||
// SAFETY:
|
||||
//
|
||||
// Only us can have a reference to these slices of the buffer
|
||||
for element in unsafe { &*second_slice.get() }
|
||||
{
|
||||
// SAFETY
|
||||
//
|
||||
// If this element is in a reader returned by self.read
|
||||
// with no pop called, we know it is initialized
|
||||
let init_element = unsafe { element.assume_init_ref() };
|
||||
let sat = predicate(init_element);
|
||||
if !sat
|
||||
{
|
||||
// Stop here
|
||||
// Trim second slice
|
||||
reader.second_len = second_kept;
|
||||
unsafe {
|
||||
reader.second = Some(std::mem::transmute::<
|
||||
&[MaybeUninit<T>],
|
||||
&UnsafeCell<[MaybeUninit<T>]>,
|
||||
>(
|
||||
&(&*second_slice.get())[0..first_kept]
|
||||
));
|
||||
}
|
||||
return reader;
|
||||
}
|
||||
second_kept += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> StreamWriter<'a, T>
|
||||
{
|
||||
pub fn len(&self) -> usize
|
||||
{
|
||||
self.first_len + self.second_len
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool
|
||||
{
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn next_index(&self) -> usize
|
||||
{
|
||||
self.start_index + self.written.get()
|
||||
}
|
||||
|
||||
pub fn push(&self, element: T) -> Result<(), T>
|
||||
{
|
||||
if self.written.get() < self.first_len
|
||||
{
|
||||
unsafe {
|
||||
(&mut *self.first.get())[self.written.get()] = MaybeUninit::new(element);
|
||||
}
|
||||
self.written.set(self.written.get() + 1);
|
||||
Ok(())
|
||||
}
|
||||
else if let Some(second) = &self.second
|
||||
&& self.written.get() - self.first_len < self.second_len
|
||||
{
|
||||
unsafe {
|
||||
(&mut *second.get())[self.written.get() - self.first_len] =
|
||||
MaybeUninit::new(element);
|
||||
}
|
||||
self.written.set(self.written.get() + 1);
|
||||
Ok(())
|
||||
}
|
||||
else
|
||||
{
|
||||
Err(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> StreamReader<'a, T>
|
||||
{
|
||||
pub fn len(&self) -> usize
|
||||
{
|
||||
self.first_len + self.second_len
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool
|
||||
{
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn last_index(&self) -> usize
|
||||
{
|
||||
self.start_index + self.len()
|
||||
}
|
||||
|
||||
pub fn next_index(&self) -> usize
|
||||
{
|
||||
self.start_index + self.read.get()
|
||||
}
|
||||
|
||||
pub fn pop_with_index(&self) -> Option<(T, usize)>
|
||||
{
|
||||
let index = self.next_index();
|
||||
self.pop().map(|t| (t, index))
|
||||
}
|
||||
|
||||
pub fn peek<F, O>(&self, peeker: F) -> Option<O>
|
||||
where
|
||||
F: Fn(&T) -> O,
|
||||
{
|
||||
// Same as pop, without taking, or increasing read count
|
||||
if self.read.get() < self.first_len
|
||||
{
|
||||
// SAFETY:
|
||||
//
|
||||
// If element is in this slice, it is initialized.
|
||||
// We take it once since read increases
|
||||
let element = unsafe { (&mut *self.first.get())[self.read.get()].assume_init_ref() };
|
||||
Some(peeker(element))
|
||||
}
|
||||
else if let Some(second) = &self.second
|
||||
&& self.read.get() - self.first_len < self.second_len
|
||||
{
|
||||
let element =
|
||||
unsafe { (&mut *second.get())[self.read.get() - self.first_len].assume_init_ref() };
|
||||
Some(peeker(element))
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop(&self) -> Option<T>
|
||||
{
|
||||
if self.read.get() < self.first_len
|
||||
{
|
||||
// SAFETY:
|
||||
//
|
||||
// If element is in this slice, it is initialized.
|
||||
// We take it once since read increases
|
||||
let element = unsafe {
|
||||
std::mem::replace(
|
||||
&mut (&mut *self.first.get())[self.read.get()],
|
||||
MaybeUninit::uninit(),
|
||||
)
|
||||
.assume_init()
|
||||
};
|
||||
self.read.set(self.read.get() + 1);
|
||||
Some(element)
|
||||
}
|
||||
else if let Some(second) = &self.second
|
||||
&& self.read.get() - self.first_len < self.second_len
|
||||
{
|
||||
let element = unsafe {
|
||||
std::mem::replace(
|
||||
&mut (&mut *second.get())[self.read.get() - self.first_len],
|
||||
MaybeUninit::uninit(),
|
||||
)
|
||||
.assume_init()
|
||||
};
|
||||
self.read.set(self.read.get() + 1);
|
||||
Some(element)
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When a Stream writer goes out of scope, it wrote
|
||||
// some things into the stream. These things need to be commited to the queue
|
||||
impl<'a, T> Drop for StreamWriter<'a, T>
|
||||
{
|
||||
fn drop(&mut self)
|
||||
{
|
||||
// Advance head.
|
||||
// We know that this value hasn't changed since this StreamWriter was created
|
||||
let head = self.producer.inner.head.load(Ordering::Relaxed);
|
||||
|
||||
// We want writes to the buffer to be visible when acquired in the pop side
|
||||
self.producer
|
||||
.inner
|
||||
.head
|
||||
.store(head + self.written.get(), Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
// When a Stream reader goes out of scope, it took
|
||||
// some things from the stream. These things need to be de-commited to the queue
|
||||
impl<'a, T> Drop for StreamReader<'a, T>
|
||||
{
|
||||
fn drop(&mut self)
|
||||
{
|
||||
// Advance tail.
|
||||
// We know that this value hasn't changed since this StreamWriter was created
|
||||
let tail = self.producer.inner.tail.load(Ordering::Relaxed);
|
||||
|
||||
// We want writes to the buffer to be visible when acquired in the push side
|
||||
self.producer
|
||||
.inner
|
||||
.tail
|
||||
.store(tail + self.read.get(), Ordering::Release);
|
||||
}
|
||||
}
|
||||
// impl<T: Copy> StreamConsumer<T>
|
||||
// {
|
||||
// pub fn read(&mut self) -> (&[T], &[T])
|
||||
// {
|
||||
// let (slice_1, slice_2) = self.read_takable();
|
||||
// unsafe { (std::mem::transmute(slice_1), std::mem::transmute(slice_2)) }
|
||||
// }
|
||||
// }
|
||||
|
||||
mod test
|
||||
{
|
||||
#[allow(unused_imports)]
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::stream::bounded_queue;
|
||||
|
||||
@ -693,57 +457,76 @@ mod test
|
||||
let (mut tx, mut rx) = bounded_queue::<usize>(4);
|
||||
|
||||
{
|
||||
let writer = tx.write();
|
||||
let mut writer = tx.write();
|
||||
let (a, b) = writer.slices_mut();
|
||||
assert_eq!(a.len(), 4);
|
||||
assert_eq!(b.len(), 0);
|
||||
|
||||
assert_eq!(writer.len(), 4);
|
||||
a[0] = MaybeUninit::new(0);
|
||||
a[1] = MaybeUninit::new(1);
|
||||
a[2] = MaybeUninit::new(2);
|
||||
a[3] = MaybeUninit::new(3);
|
||||
|
||||
assert_eq!(writer.push(1), Ok(()));
|
||||
assert_eq!(writer.push(2), Ok(()));
|
||||
assert_eq!(writer.push(3), Ok(()));
|
||||
assert_eq!(writer.push(4), Ok(()));
|
||||
assert_eq!(writer.push(5), Err(5));
|
||||
tx.produce(4);
|
||||
}
|
||||
|
||||
{
|
||||
let reader = rx.read();
|
||||
let mut reader = rx.read_takable();
|
||||
let (a, b) = reader.slices_mut();
|
||||
assert_eq!(a.len(), 4);
|
||||
assert_eq!(b.len(), 0);
|
||||
|
||||
assert_eq!(reader.len(), 4);
|
||||
unsafe {
|
||||
assert_eq!(a[0].take(), 0);
|
||||
assert_eq!(a[1].take(), 1);
|
||||
assert_eq!(a[2].take(), 2);
|
||||
assert_eq!(a[3].take(), 3);
|
||||
}
|
||||
|
||||
assert_eq!(reader.pop(), Some(1));
|
||||
assert_eq!(reader.pop(), Some(2));
|
||||
assert_eq!(reader.pop(), Some(3));
|
||||
assert_eq!(reader.pop(), Some(4));
|
||||
assert_eq!(reader.pop(), None);
|
||||
rx.consume(4);
|
||||
}
|
||||
|
||||
// Put stream into weird situatino
|
||||
// Put stream into weird situation
|
||||
{
|
||||
let writer = tx.write();
|
||||
assert_eq!(writer.push(1), Ok(()));
|
||||
assert_eq!(writer.push(2), Ok(()));
|
||||
assert_eq!(writer.push(3), Ok(()));
|
||||
assert_eq!(writer.push(4), Ok(()));
|
||||
let mut writer = tx.write();
|
||||
let (a, b) = writer.slices_mut();
|
||||
assert_eq!(a.len(), 4);
|
||||
assert_eq!(b.len(), 0);
|
||||
|
||||
a[0] = MaybeUninit::new(0);
|
||||
a[1] = MaybeUninit::new(1);
|
||||
a[2] = MaybeUninit::new(2);
|
||||
|
||||
tx.produce(3);
|
||||
}
|
||||
|
||||
{
|
||||
let reader = rx.read();
|
||||
assert_eq!(reader.pop(), Some(1));
|
||||
assert_eq!(reader.pop(), Some(2));
|
||||
let mut reader = rx.read_takable();
|
||||
let (a, b) = reader.slices_mut();
|
||||
assert_eq!(a.len(), 3);
|
||||
assert_eq!(b.len(), 0);
|
||||
|
||||
unsafe {
|
||||
assert_eq!(a[0].take(), 0);
|
||||
assert_eq!(a[1].take(), 1);
|
||||
assert_eq!(a[2].take(), 2);
|
||||
}
|
||||
|
||||
rx.consume(1);
|
||||
}
|
||||
|
||||
{
|
||||
let writer = tx.write();
|
||||
assert_eq!(writer.len(), 2);
|
||||
assert_eq!(writer.push(5), Ok(()));
|
||||
assert_eq!(writer.push(6), Ok(()));
|
||||
let (a, b) = writer.slices();
|
||||
assert_eq!(a.len(), 1);
|
||||
assert_eq!(b.len(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
let reader = rx.read();
|
||||
assert_eq!(reader.pop(), Some(3));
|
||||
assert_eq!(reader.pop(), Some(4));
|
||||
assert_eq!(reader.pop(), Some(5));
|
||||
assert_eq!(reader.pop(), Some(6));
|
||||
let reader = rx.read_takable();
|
||||
let (a, b) = reader.slices();
|
||||
assert_eq!(a.len(), 2);
|
||||
assert_eq!(b.len(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,91 @@
|
||||
/// A tag is an amount of data you can "attach" to a particular sample in stream.
|
||||
/// Indeed you might need to mark sample, or bring information along with them sparsely :
|
||||
/// Tags are often a rare occurence.
|
||||
///
|
||||
/// Example :
|
||||
///
|
||||
/// Timing error detection : A sample is the center of a symbol
|
||||
/// There might be a Tag :
|
||||
///
|
||||
/// ted_symbol_center: 0.011 (Here the number might represent a error value)
|
||||
///
|
||||
/// But really, any kind of data could be referenced by a tag :
|
||||
/// A float, A string like a json structure, or even a binary blob.
|
||||
///
|
||||
/// A tag is formed that way :
|
||||
///
|
||||
/// The TagKey :
|
||||
/// | A unique key allocated at the begining,
|
||||
/// | Which is tied to a human readable label configured
|
||||
/// | ahead of runtime.
|
||||
/// |
|
||||
/// | And a type, which is used to constrain the type that the tag
|
||||
/// | contains, to keep downcasting safe and less error prone.
|
||||
///
|
||||
/// The TagData :
|
||||
/// | Then, when it is time to tag sa sample, a block uses that tag key
|
||||
/// | to add a tag on a sample bundling the tag key, and the sepcific data.
|
||||
///
|
||||
///
|
||||
/// Another block downstream could then check if a sample has been tagged with something
|
||||
/// of interest with the tag key.
|
||||
/// If so, the block can then retrieve the data on the tag.
|
||||
///
|
||||
/// Each sample can have multiple tags attached to it.
|
||||
///
|
||||
/// TagKey using a usize backed identifier is used instead of the more
|
||||
/// classic way of using String-Value pairs to allow easier and faster
|
||||
/// Comparisons, as well as much tighter and cleaner managment of tags
|
||||
/// throughout the graph.
|
||||
///
|
||||
///
|
||||
///
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::RwLock;
|
||||
|
||||
// Tags a particular sample within a specific stream
|
||||
/// Object to allocate tags and give a unique identifier per tag
|
||||
struct TagAllocator
|
||||
{
|
||||
// Counter to uniquely identify allocated tags
|
||||
counter: usize,
|
||||
|
||||
// Keeps readable tag type and label(s) for the tags
|
||||
labels: HashMap<usize, (&'static str, TagLabel)>,
|
||||
}
|
||||
|
||||
/// Label for a tag like : "symbol", "packet_start", "error"
|
||||
struct TagLabel
|
||||
{
|
||||
// TODO: Allow user customization of labels
|
||||
// maybe multiple labels
|
||||
_label: String,
|
||||
}
|
||||
|
||||
/// Object from which TagKeys are obtained. This guarantees absence of collisions between tags
|
||||
pub struct Tags
|
||||
{
|
||||
allocator: Arc<RwLock<TagAllocator>>,
|
||||
}
|
||||
|
||||
/// Used to anotate tags entries and retrieve them
|
||||
#[derive(Clone)]
|
||||
pub struct Tag
|
||||
pub struct TagKey<T>
|
||||
{
|
||||
key: usize,
|
||||
|
||||
// Maybe used later to retrieve labels for example
|
||||
_owner: Arc<RwLock<TagAllocator>>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// Tags a particular sample within a specific stream
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TagSlot
|
||||
{
|
||||
// Position of the sample this tag is tied to.
|
||||
// The position is in terms of the stream front index when the
|
||||
@ -17,19 +95,157 @@ pub struct Tag
|
||||
// TODO: Make it such that when a tag is duplicated, the data seems to be too:
|
||||
// When adding on a duplicate, it should not replicate on others, but without
|
||||
// requiring a deep copy.
|
||||
pub data: Arc<Mutex<HashMap<String, Arc<dyn Any + Send + Sync>>>>,
|
||||
pub tag: Tag,
|
||||
}
|
||||
|
||||
/// A Tag object containing TagKey-value pairs
|
||||
#[derive(Clone)]
|
||||
pub struct Tag
|
||||
{
|
||||
data: Arc<RwLock<HashMap<usize, Arc<dyn Any + Send + Sync + 'static>>>>,
|
||||
}
|
||||
|
||||
impl Tags
|
||||
{
|
||||
/// Creates a new tag allocator
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
allocator: Arc::new(RwLock::new(TagAllocator {
|
||||
counter: 0,
|
||||
labels: HashMap::default(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates a new unique tag key
|
||||
pub fn allocate_tag<T>(&mut self, label: impl AsRef<str>) -> TagKey<T>
|
||||
{
|
||||
let k = self.allocator.write().unwrap().allocate_tag::<T>(label);
|
||||
TagKey {
|
||||
key: k,
|
||||
_owner: self.allocator.clone(),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TagAllocator
|
||||
{
|
||||
/// Allocates a new unique tag key
|
||||
pub fn allocate_tag<T>(&mut self, label: impl AsRef<str>) -> usize
|
||||
{
|
||||
let key = self.counter;
|
||||
self.labels.insert(
|
||||
self.counter,
|
||||
(
|
||||
std::any::type_name::<T>(),
|
||||
TagLabel {
|
||||
_label: label.as_ref().to_owned(),
|
||||
},
|
||||
),
|
||||
);
|
||||
self.counter += 1;
|
||||
key
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Tags
|
||||
{
|
||||
/// Creates a new tag allocator
|
||||
fn default() -> Self
|
||||
{
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Tag
|
||||
{
|
||||
pub fn merge_tag_opts<const N: usize>(tag_opts: [Option<Tag>; N]) -> Option<Tag>
|
||||
/// Creates a new empty tag
|
||||
pub fn new() -> Self
|
||||
{
|
||||
let mut out_tag = None;
|
||||
for tag in tag_opts.iter()
|
||||
{
|
||||
out_tag = out_tag.merge(tag);
|
||||
Self {
|
||||
data: Default::default(),
|
||||
}
|
||||
out_tag
|
||||
}
|
||||
|
||||
/// Creates a new tag with a (key, value) entry
|
||||
pub fn with_entry<T: 'static + Send + Sync>(key: TagKey<T>, value: T) -> Self
|
||||
{
|
||||
let new_tag = Self::default();
|
||||
new_tag.add_entry(key, value);
|
||||
new_tag
|
||||
}
|
||||
|
||||
/// Creates a new tag, which is the combination of the given tags
|
||||
pub fn from_tags<const N: usize>(tag_opts: &[&Tag; N]) -> Tag
|
||||
{
|
||||
let new_tag = Self::default();
|
||||
{
|
||||
let mut writer = new_tag.data.write().unwrap();
|
||||
|
||||
for tag in tag_opts.iter()
|
||||
{
|
||||
let reader = tag.data.read().unwrap();
|
||||
writer.extend(reader.iter().map(|x| (*x.0, x.1.clone())));
|
||||
}
|
||||
}
|
||||
new_tag
|
||||
}
|
||||
|
||||
/// Creates a new tag option, which is the combination of the given tag options
|
||||
///
|
||||
/// If all the tag options are None, None is returned
|
||||
/// Otherwise it is Some of the combination of all of the tags which are Some
|
||||
pub fn from_tag_opts<'a>(mut tag_opts: impl Iterator<Item = &'a Option<Tag>>) -> Option<Tag>
|
||||
{
|
||||
|
||||
let new_tag = Self::default();
|
||||
let mut some_tags = 0;
|
||||
{
|
||||
let mut writer = new_tag.data.write().unwrap();
|
||||
|
||||
for tag in tag_opts.filter(|t| t.is_some())
|
||||
{
|
||||
some_tags += 1;
|
||||
let reader = tag.as_ref().unwrap().data.read().unwrap();
|
||||
writer.extend(reader.iter().map(|x| (*x.0, x.1.clone())));
|
||||
}
|
||||
}
|
||||
if some_tags > 0
|
||||
{
|
||||
Some(new_tag)
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a new entry in the tag. If it already exists, it is overwritten
|
||||
pub fn add_entry<T: 'static + Send + Sync>(&self, key: TagKey<T>, value: T)
|
||||
{
|
||||
self.data.write().unwrap().insert(key.key, Arc::new(value));
|
||||
}
|
||||
|
||||
/// Retrieves an entry in tag. If ther is no such entry corresponding to the key,
|
||||
/// retruns None
|
||||
pub fn retrieve<T: 'static + Send + Sync>(&self, key: &TagKey<T>) -> Option<Arc<T>>
|
||||
{
|
||||
let element = self.data.read().unwrap().get(&key.key).cloned();
|
||||
|
||||
// TODO: When available : downcast unchecked, the type should be guaranteed
|
||||
// by the TagKey's generic
|
||||
// (But there might be an issue if the key comes from somewhere else)
|
||||
element.map(|x| x.downcast().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Tag
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,19 +261,7 @@ impl TagMergable<Tag> for Tag
|
||||
{
|
||||
fn merge(&self, other: &Self) -> Self
|
||||
{
|
||||
// TODO: More performant merge
|
||||
let mut new = other.clone();
|
||||
|
||||
new.position = self.position;
|
||||
{
|
||||
let mut data_locked = new.data.lock().unwrap();
|
||||
for (k, v) in self.data.lock().unwrap().iter()
|
||||
{
|
||||
data_locked.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
new
|
||||
Self::from_tags(&[self, other])
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,61 +269,72 @@ impl TagMergable<Option<Tag>> for Option<Tag>
|
||||
{
|
||||
fn merge(&self, other: &Self) -> Self
|
||||
{
|
||||
match self
|
||||
{
|
||||
Some(first) => match other
|
||||
{
|
||||
Some(other) => Some(first.merge(other)),
|
||||
None => Some(first.clone()),
|
||||
},
|
||||
None => other.clone(),
|
||||
}
|
||||
Tag::from_tag_opts([self, other].into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> TagMergable<Option<Tag>> for Tagged<T>
|
||||
{
|
||||
fn merge(&self, other: &Option<Tag>) -> Self
|
||||
{
|
||||
Tagged::new(self.0.clone(), self.1.merge(other))
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a data, with a potential tag attached to it.
|
||||
#[derive(Clone)]
|
||||
pub struct Tagged<T>
|
||||
{
|
||||
inner: T,
|
||||
tag: Option<Tag>,
|
||||
}
|
||||
pub struct Tagged<T>(pub T, pub Option<Tag>);
|
||||
|
||||
impl<T> Tagged<T>
|
||||
{
|
||||
pub fn new(inner: T, tag: Option<Tag>) -> Self
|
||||
{
|
||||
Self { inner, tag }
|
||||
Self(inner, tag)
|
||||
}
|
||||
|
||||
pub fn has_tag(&self) -> bool
|
||||
{
|
||||
self.tag.is_some()
|
||||
self.1.is_some()
|
||||
}
|
||||
|
||||
pub fn strip(&mut self)
|
||||
{
|
||||
self.tag = None;
|
||||
self.1 = None;
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T
|
||||
{
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn tag(&mut self, tag: Tag) -> Option<Tag>
|
||||
{
|
||||
let t = self.tag.take();
|
||||
self.tag = Some(tag);
|
||||
let t = self.1.take();
|
||||
self.1 = Some(tag);
|
||||
t
|
||||
}
|
||||
|
||||
pub fn retrieve<D: Send + Sync + 'static>(&self, key: &TagKey<D>) -> Option<Arc<D>>
|
||||
{
|
||||
self.1.as_ref().and_then(|t| t.retrieve(key))
|
||||
}
|
||||
|
||||
pub fn add_entry<D: Send + Sync + 'static>(&mut self, key: TagKey<D>, value: D)
|
||||
{
|
||||
self.1.get_or_insert(Tag::default()).add_entry(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Tagged<T>
|
||||
{
|
||||
pub fn stripped(&self) -> Self
|
||||
{
|
||||
self.inner.clone().into()
|
||||
self.0.clone().into()
|
||||
}
|
||||
|
||||
pub fn tagged(&self, tag: Tag) -> Self
|
||||
{
|
||||
(self.inner.clone(), tag).into()
|
||||
(self.0.clone(), tag).into()
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,11 +354,27 @@ impl<T> From<(T, Tag)> for Tagged<T>
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<(T, Option<Tag>)> for Tagged<T>
|
||||
{
|
||||
fn from((value, tag): (T, Option<Tag>)) -> Self
|
||||
{
|
||||
Self::new(value, tag)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Tagged<T>> for (T, Option<Tag>)
|
||||
{
|
||||
fn from(val: Tagged<T>) -> Self
|
||||
{
|
||||
(val.0, val.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Tagged<T>
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target
|
||||
{
|
||||
&mut self.inner
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,6 +384,6 @@ impl<T> Deref for Tagged<T>
|
||||
|
||||
fn deref(&self) -> &Self::Target
|
||||
{
|
||||
&self.inner
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user