Working fsk transmitter
This commit is contained in:
221
Cargo.lock
generated
221
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"
|
||||
@ -451,6 +473,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 +623,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 +734,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"
|
||||
@ -719,6 +814,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"
|
||||
@ -1016,6 +1117,7 @@ dependencies = [
|
||||
name = "example"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cpal",
|
||||
"eframe",
|
||||
"egui",
|
||||
"egui_plot",
|
||||
@ -1023,6 +1125,7 @@ dependencies = [
|
||||
"num",
|
||||
"oxydsp-dsp",
|
||||
"oxydsp-flowgraph",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1210,6 +1313,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"rand_core",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
@ -1741,6 +1845,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 +2017,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 +2132,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 +2154,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 +2186,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 +2198,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 +2233,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 +2245,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 +2270,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 +2282,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 +2301,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 +2314,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 +2337,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 +2350,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 +2362,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 +2385,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 +2405,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 +2417,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",
|
||||
@ -2556,6 +2732,23 @@ 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 = "range-alloc"
|
||||
version = "0.1.5"
|
||||
@ -4096,7 +4289,7 @@ dependencies = [
|
||||
"android-activity",
|
||||
"atomic-waker",
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"block2 0.5.1",
|
||||
"bytemuck",
|
||||
"calloop 0.13.0",
|
||||
"cfg_aliases",
|
||||
|
||||
@ -11,3 +11,5 @@ 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
example/mod.wav
BIN
example/mod.wav
Binary file not shown.
@ -3,21 +3,23 @@
|
||||
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} }"];
|
||||
MapResultTagged_6 [label="{ {<i0> input}| MapResultTagged |{<o0> output} }"];
|
||||
NullSink_7 [label="{ {<i0> input}| NullSink }"];
|
||||
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 -> 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 -> MapResultTagged_6:i0 [label="num_complex::Complex<f32>"];
|
||||
MapResultTagged_6:o0 -> NullSink_7:i0 [label="num_complex::Complex<f32>"];
|
||||
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>"];
|
||||
|
||||
}
|
||||
|
||||
@ -1,394 +1,62 @@
|
||||
use std::fmt::Display;
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::sync::mpsc;
|
||||
|
||||
use cpal::traits::DeviceTrait;
|
||||
use cpal::traits::HostTrait;
|
||||
use eframe::NativeOptions;
|
||||
use egui::Color32;
|
||||
use egui_plot::Line;
|
||||
use egui_plot::MarkerShape;
|
||||
use egui_plot::PlotPoints;
|
||||
use egui_plot::Points;
|
||||
use egui_plot::Polygon;
|
||||
use egui_plot::VLine;
|
||||
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::math::basic::Adder;
|
||||
use oxydsp_dsp::blocks::math::basic::Multiplier;
|
||||
use oxydsp_dsp::blocks::synthesis::Nco;
|
||||
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::MapResult;
|
||||
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::channels::TxSink;
|
||||
use oxydsp_dsp::blocks::utilities::adapters::ScanTagged;
|
||||
use oxydsp_dsp::blocks::utilities::channels::RxSource;
|
||||
use oxydsp_dsp::blocks::utilities::iter::IterSource;
|
||||
use oxydsp_dsp::blocks::utilities::squelch::Squelch;
|
||||
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::block::SyncBlock;
|
||||
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::io::stream;
|
||||
use oxydsp_flowgraph::sync_block;
|
||||
use oxydsp_flowgraph::tag::Tag;
|
||||
use oxydsp_flowgraph::tag::Tagged;
|
||||
use rand::random;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
//#[sync_block(tagged)]
|
||||
pub struct Printer<T: 'static + Display>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
use crate::transmitter::Transmitter;
|
||||
|
||||
n: usize,
|
||||
}
|
||||
|
||||
impl<T: 'static + Display> Block for Printer<T>
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
for x in self.input.pop_iter()
|
||||
{
|
||||
if self.n.is_multiple_of(100_000)
|
||||
{
|
||||
if x.has_tag()
|
||||
{
|
||||
let tag = x.1.unwrap();
|
||||
let valuea: usize = *tag.retrieve("valuea").unwrap().downcast().unwrap();
|
||||
let valueb: usize = *tag.retrieve("valueb").unwrap().downcast().unwrap();
|
||||
println!(
|
||||
"{} TAGGED {}, {} amoziefjmoazijfmoazeijfmoazeifjmozeijfmoizfmojzaemfojzaemofjzeamofimazoijefmzoaeijfmoazeifj",
|
||||
x.0, valuea, valueb
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
println!("{} NO TAG", x.0);
|
||||
}
|
||||
}
|
||||
self.n += 1
|
||||
}
|
||||
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'view, T: 'static + Display> SyncBlock<'view> for Printer<T>
|
||||
// {
|
||||
// fn sync_work(state: Self::StateView, input: Self::Input) -> Option<Self::Output>
|
||||
// {
|
||||
// // if state.n.is_multiple_of(100_000)
|
||||
// // {
|
||||
// if input.has_tag()
|
||||
// {
|
||||
// println!("{} TAGGED", input.0);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// println!("{} NO TAG", input.0);
|
||||
// }
|
||||
// //}
|
||||
// *state.n += 1;
|
||||
// Some(())
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(BlockIO)]
|
||||
//#[sync_block(tagged)]
|
||||
pub struct SourceTag
|
||||
{
|
||||
#[output]
|
||||
output: Out<usize>,
|
||||
|
||||
tk: String,
|
||||
|
||||
n: usize,
|
||||
}
|
||||
|
||||
impl SourceTag
|
||||
{
|
||||
pub fn new(tk: String) -> (Self, In<usize>)
|
||||
{
|
||||
let (output, input) = stream();
|
||||
(Self { output, tk, n: 0 }, input)
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'view> SyncBlock<'view> for SourceTag
|
||||
// {
|
||||
// fn sync_work(state: Self::StateView, _input: Self::Input) -> Option<Self::Output>
|
||||
// {
|
||||
// *state.n += 1;
|
||||
//
|
||||
// let t = Tag::default();
|
||||
// Some((*state.n, t).into())
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Block for SourceTag
|
||||
{
|
||||
fn work(&mut self) -> BlockResult
|
||||
{
|
||||
self.output.push_iter((0usize..).map(|_| {
|
||||
self.n += 1;
|
||||
(
|
||||
self.n - 1,
|
||||
if (self.n - 1).is_multiple_of(1_000)
|
||||
{
|
||||
let tag = Tag::default();
|
||||
tag.tag(&self.tk, self.n);
|
||||
Some(tag)
|
||||
}
|
||||
else
|
||||
{
|
||||
None
|
||||
},
|
||||
)
|
||||
.into()
|
||||
}));
|
||||
BlockResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
// 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 + Display> Printer<T>
|
||||
{
|
||||
pub fn new(input: In<T>) -> Self
|
||||
{
|
||||
Self { input, n: 0 }
|
||||
}
|
||||
}
|
||||
pub mod receiver;
|
||||
pub mod transmitter;
|
||||
|
||||
fn main()
|
||||
{
|
||||
main_demod()
|
||||
}
|
||||
let tx = Transmitter::start_new();
|
||||
|
||||
fn main_tst()
|
||||
{
|
||||
let (sourcea, a) = SourceTag::new("valuea".to_string());
|
||||
let (sourceb, b) = SourceTag::new("valueb".to_string());
|
||||
let (adder, a) = Adder::new(a, b);
|
||||
let printer = Printer::new(a);
|
||||
|
||||
let fg = flowgraph![sourcea, sourceb, adder, printer];
|
||||
let _ = fg.run().join();
|
||||
}
|
||||
|
||||
const SAMPLE_RATE: usize = 48_000;
|
||||
const SAMPLE_PER_SYMBOL: usize = 96;
|
||||
const DEVIATION: f64 = 500.;
|
||||
const CARRIER: f64 = 1000.;
|
||||
|
||||
fn main_demod()
|
||||
{
|
||||
let carrier = DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64);
|
||||
|
||||
let mut reader = hound::WavReader::open("mod.wav").unwrap();
|
||||
let sqr_sum = reader
|
||||
.samples::<i16>()
|
||||
.map(|sample| (sample.unwrap() as f32) / (i16::MAX as f32))
|
||||
.collect::<Vec<_>>();
|
||||
let (iter_source, signal) = IterSource::new(sqr_sum.into_iter());
|
||||
|
||||
// Make an iq sampler
|
||||
let (lo, lo_signal) = OscillatorSource::new(carrier.into());
|
||||
let (mixer, iq) = Multiplier::new(signal, lo_signal);
|
||||
let (iq_bandpass, iq) = FirFilter::<Complex<f32>, Complex<f32>, Complex<f32>>::new(
|
||||
iq,
|
||||
Fir::lowpass(carrier, 100).normalized(),
|
||||
);
|
||||
let (arg_extract, arg) = Scan::new(iq, Complex::zero(), |state, sample| {
|
||||
let angle = *state / sample;
|
||||
*state = sample;
|
||||
angle.arg()
|
||||
});
|
||||
let (sig_lowpass, arg) =
|
||||
FirFilter::<f32, f32, f32>::new(arg, Fir(vec![1.; SAMPLE_PER_SYMBOL]).normalized());
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let elg_loop = Fir(vec![1.0f32; 10]);
|
||||
let mut elg_loop = elg_loop.normalized();
|
||||
elg_loop.0[0] = 0.5;
|
||||
let (elg, arg) = EarlyLateGate::new(arg, elg_loop, SAMPLE_PER_SYMBOL);
|
||||
let (sender, arg) = MapResultTagged::new(arg, move |x| {
|
||||
let _ = tx.send((
|
||||
x.0,
|
||||
x.1.as_ref()
|
||||
.is_some_and(|t| t.retrieve("elg_symbol").is_some()),
|
||||
));
|
||||
if x.1
|
||||
.is_some_and(|t| t.retrieve("itersource_finished").is_some())
|
||||
{
|
||||
println!("FINISHED !");
|
||||
(x.0.into(), BlockResult::Exit)
|
||||
}
|
||||
else
|
||||
{
|
||||
(x.0.into(), BlockResult::Ok)
|
||||
}
|
||||
});
|
||||
let null_sink = NullSink::new(arg);
|
||||
|
||||
let graph = flowgraph![
|
||||
iter_source,
|
||||
lo,
|
||||
mixer,
|
||||
iq_bandpass,
|
||||
arg_extract,
|
||||
sig_lowpass,
|
||||
elg,
|
||||
sender,
|
||||
null_sink
|
||||
];
|
||||
let j = graph.run();
|
||||
|
||||
let mut output = vec![];
|
||||
while let Ok(x) = rx.recv()
|
||||
loop
|
||||
{
|
||||
output.push(x);
|
||||
let mut user_input = String::new();
|
||||
io::stdin().read_line(&mut user_input).unwrap();
|
||||
println!("Transmitting ...");
|
||||
tx.transmit(user_input.as_bytes().to_vec());
|
||||
}
|
||||
let _ = j.join();
|
||||
|
||||
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.0 as f64])
|
||||
.collect::<PlotPoints>(),
|
||||
));
|
||||
|
||||
plot_ui.points(
|
||||
Points::new(
|
||||
"symbols",
|
||||
output
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, (_, x))| *x)
|
||||
.map(|(i, (s, _))| [i as f64, *s as f64])
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.id("symbols")
|
||||
.radius(5.)
|
||||
.shape(MarkerShape::Diamond),
|
||||
);
|
||||
});
|
||||
ctx.request_repaint();
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn main_mod()
|
||||
{
|
||||
let carrier = DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64);
|
||||
let deviation = DigitalFrequency::from_time_frequency(DEVIATION, 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 (sender, passband) = MapResultTagged::new(passband, move |x| {
|
||||
let _ = tx.send(x.0);
|
||||
if x.1
|
||||
.is_some_and(|t| t.retrieve("itersource_finished").is_some())
|
||||
{
|
||||
println!("FINISHED !");
|
||||
(x.0.into(), BlockResult::Exit)
|
||||
}
|
||||
else
|
||||
{
|
||||
(x.0.into(), BlockResult::Ok)
|
||||
}
|
||||
});
|
||||
let null_sink = NullSink::new(passband);
|
||||
|
||||
let graph = flowgraph![
|
||||
bit_stream,
|
||||
to_freq,
|
||||
repeat,
|
||||
base_oscillator,
|
||||
local_oscillator,
|
||||
frontend,
|
||||
sender,
|
||||
null_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: SAMPLE_RATE as u32,
|
||||
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 const SAMPLE_RATE: usize = 48_000;
|
||||
pub const SAMPLE_PER_SYMBOL: usize = 96;
|
||||
pub const DEVIATION: f64 = 500.;
|
||||
pub const CARRIER: f64 = 1700.;
|
||||
|
||||
pub fn to_bits(n: u8) -> [bool; 8]
|
||||
{
|
||||
|
||||
178
example/src/receiver.rs
Normal file
178
example/src/receiver.rs
Normal file
@ -0,0 +1,178 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::mpsc;
|
||||
|
||||
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::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 crate::CARRIER;
|
||||
use crate::DEVIATION;
|
||||
use crate::SAMPLE_PER_SYMBOL;
|
||||
use crate::SAMPLE_RATE;
|
||||
|
||||
fn main_demod()
|
||||
{
|
||||
let carrier = DigitalFrequency::from_time_frequency(CARRIER, SAMPLE_RATE as f64);
|
||||
|
||||
let mut reader = hound::WavReader::open("mod.wav").unwrap();
|
||||
let sqr_sum = reader
|
||||
.samples::<i16>()
|
||||
.map(|sample| (sample.unwrap() as f32) / (i16::MAX as f32))
|
||||
.collect::<Vec<_>>();
|
||||
let (audio_tx, audio_rx) = mpsc::channel();
|
||||
|
||||
//let (source, signal) = IterSource::new(sqr_sum.into_iter().cycle());
|
||||
let (source, signal) = RxSource::new(audio_rx);
|
||||
|
||||
let (mut zero_if, iq) = ZeroIf::new(signal, carrier.into());
|
||||
|
||||
zero_if.set_fir(Fir::lowpass(
|
||||
DigitalFrequency::from_time_frequency(DEVIATION * 2. + 100., SAMPLE_RATE as f64),
|
||||
200,
|
||||
));
|
||||
|
||||
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() * 14.
|
||||
});
|
||||
let (sig_lowpass, arg) =
|
||||
FirFilter::<f32, f32, f32>::new(arg, Fir(vec![1.; SAMPLE_PER_SYMBOL]).normalized());
|
||||
|
||||
let mut elg_loop = Fir(vec![1. / 30.; 30]);
|
||||
//let mut elg_loop = elg_loop.normalized();
|
||||
*elg_loop.0.last_mut().unwrap() = 0.4;
|
||||
//*elg_loop.0.first_mut().unwrap() = 0.001;
|
||||
let (elg, arg) = EarlyLateGate::new(arg, elg_loop, SAMPLE_PER_SYMBOL);
|
||||
|
||||
// Eye diagram
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let (eye_sender, arg) = ScanTagged::new(arg, VecDeque::new(), move |history, x| {
|
||||
if history.len() == 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("elg_symbol")
|
||||
{
|
||||
error = *err.downcast().unwrap();
|
||||
true
|
||||
}
|
||||
else
|
||||
{
|
||||
false
|
||||
}
|
||||
});
|
||||
history.push_front(((is_symbol_center, error), x.0));
|
||||
|
||||
if history.len() > SAMPLE_PER_SYMBOL / 2 && history[SAMPLE_PER_SYMBOL / 2].0.0
|
||||
{
|
||||
let _ = tx.send((
|
||||
history.iter().map(|(_, x)| *x).collect::<Vec<_>>(),
|
||||
history[SAMPLE_PER_SYMBOL / 2].0.1,
|
||||
));
|
||||
}
|
||||
|
||||
x.0.into()
|
||||
});
|
||||
let null_sink = NullSink::new(arg);
|
||||
|
||||
let graph = flowgraph![
|
||||
source,
|
||||
squelch,
|
||||
zero_if,
|
||||
arg_extract,
|
||||
sig_lowpass,
|
||||
elg,
|
||||
eye_sender,
|
||||
null_sink
|
||||
];
|
||||
|
||||
// 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
|
||||
);
|
||||
|
||||
let j = graph.run();
|
||||
|
||||
let mut eyes = VecDeque::new();
|
||||
eframe::run_simple_native("Plot", NativeOptions::default(), move |ctx, _frame| {
|
||||
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, *s as f64])
|
||||
.collect::<PlotPoints>(),
|
||||
)
|
||||
.id("eyes")
|
||||
.color(color_from_err(eye.1, 0.3)),
|
||||
);
|
||||
}
|
||||
});
|
||||
ctx.request_repaint();
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
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.)))
|
||||
}
|
||||
268
example/src/transmitter.rs
Normal file
268
example/src/transmitter.rs
Normal file
@ -0,0 +1,268 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::iter::FusedIterator;
|
||||
use std::ops::BitXor;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::sync::mpsc::SyncSender;
|
||||
use std::sync::mpsc::sync_channel;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use cpal::Stream;
|
||||
use cpal::traits::DeviceTrait;
|
||||
use cpal::traits::HostTrait;
|
||||
use eframe::NativeOptions;
|
||||
use egui::Color32;
|
||||
use egui::output;
|
||||
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::math::basic::Adder;
|
||||
use oxydsp_dsp::blocks::math::basic::Multiplier;
|
||||
use oxydsp_dsp::blocks::synthesis::Nco;
|
||||
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::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::channels::RxSource;
|
||||
use oxydsp_dsp::blocks::utilities::channels::TxSink;
|
||||
use oxydsp_dsp::blocks::utilities::iter::IterSource;
|
||||
use oxydsp_dsp::blocks::utilities::squelch::Squelch;
|
||||
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 crate::CARRIER;
|
||||
use crate::DEVIATION;
|
||||
use crate::SAMPLE_PER_SYMBOL;
|
||||
use crate::SAMPLE_RATE;
|
||||
use crate::gaussian;
|
||||
use crate::to_bits;
|
||||
|
||||
#[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 writer = self.output.write();
|
||||
let reader = self.input.read();
|
||||
|
||||
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.pop()
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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() + 1) as u16;
|
||||
let checksum = packet.iter().copied().reduce(BitXor::bitxor).unwrap();
|
||||
|
||||
// Learning sequence
|
||||
let mut frame = vec![0b10101010; 8];
|
||||
frame.push(packet_length.to_le_bytes()[0]);
|
||||
frame.push(packet_length.to_le_bytes()[1]);
|
||||
frame.extend(packet.iter());
|
||||
frame.push(checksum);
|
||||
frame
|
||||
.into_iter()
|
||||
.flat_map(to_bits)
|
||||
.map(|x| if x { 1. } else { -1.0f32 })
|
||||
});
|
||||
|
||||
let (repeat, bits) = Repeat::new(bits, SAMPLE_PER_SYMBOL);
|
||||
|
||||
// gaussian fir
|
||||
let fir = Fir((0..SAMPLE_PER_SYMBOL)
|
||||
.map(|x| gaussian(0.3, x as f32 / SAMPLE_PER_SYMBOL as f32))
|
||||
.collect())
|
||||
.normalized();
|
||||
|
||||
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 tx_sink = TxSink::new(passband, audio_tx);
|
||||
|
||||
let graph = flowgraph![
|
||||
packet_rec,
|
||||
linearizer,
|
||||
bit_filter,
|
||||
to_freq,
|
||||
repeat,
|
||||
base_oscillator,
|
||||
local_oscillator,
|
||||
frontend,
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
pub mod filtering;
|
||||
pub mod iq;
|
||||
pub mod math;
|
||||
pub mod synthesis;
|
||||
pub mod ted;
|
||||
|
||||
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;
|
||||
65
oxydsp-dsp/src/blocks/iq/zero_if.rs
Normal file
65
oxydsp-dsp/src/blocks/iq/zero_if.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use num::{Complex, Float};
|
||||
use oxydsp_flowgraph::{
|
||||
BlockIO,
|
||||
block::SyncBlock,
|
||||
io::{In, Out},
|
||||
sync_block,
|
||||
};
|
||||
use rustfft::FftNum;
|
||||
|
||||
use crate::{
|
||||
filtering::fir::{Fir, FirFilter},
|
||||
synthesis::oscillator::Nco,
|
||||
};
|
||||
|
||||
#[derive(BlockIO)]
|
||||
#[sync_block]
|
||||
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,
|
||||
{
|
||||
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)),
|
||||
},
|
||||
port,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_fir(&mut self, fir: Fir<Complex<T>>)
|
||||
{
|
||||
self.filter = FirFilter::new(fir);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'view, T> SyncBlock<'view> for ZeroIf<T>
|
||||
where
|
||||
T: std::clone::Clone + num::Num + Float + From<f32> + 'static + num::Float,
|
||||
{
|
||||
fn sync_work(state: Self::StateView, input: Self::Input) -> Option<Self::Output>
|
||||
{
|
||||
// Mix
|
||||
let lo_sample = state.local_oscillator.next().unwrap();
|
||||
let iq = Complex::new(input * lo_sample.re, input * lo_sample.im);
|
||||
|
||||
Some(state.filter.next(iq))
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ use std::collections::VecDeque;
|
||||
use std::iter::Sum;
|
||||
|
||||
use num::Float;
|
||||
use num::NumCast;
|
||||
use oxydsp_flowgraph::BlockIO;
|
||||
use oxydsp_flowgraph::block::SyncBlock;
|
||||
use oxydsp_flowgraph::io::In;
|
||||
@ -14,7 +15,7 @@ use crate::filtering::fir::FirFilter;
|
||||
|
||||
#[derive(BlockIO)]
|
||||
#[sync_block(tagged)]
|
||||
pub struct EarlyLateGate<T: Float + Sum + Clone + 'static>
|
||||
pub struct EarlyLateGate<T: Float + Send + Sync + Sum + Clone + NumCast + 'static>
|
||||
{
|
||||
#[input]
|
||||
input: In<T>,
|
||||
@ -34,13 +35,13 @@ pub struct EarlyLateGate<T: Float + Sum + Clone + 'static>
|
||||
|
||||
// The next window location, in relation to the last sample such that the window is centered on
|
||||
// a symbol center (hopefully)
|
||||
next_sample: usize,
|
||||
next_sample: f32,
|
||||
loop_filter: FirFilter<T, T, T>,
|
||||
}
|
||||
|
||||
impl<T> EarlyLateGate<T>
|
||||
where
|
||||
T: Float + Sum + Clone + 'static,
|
||||
T: Float + Sum + Clone + 'static + Send + Sync + NumCast,
|
||||
{
|
||||
pub fn new(input: In<T>, loop_filter: Fir<T>, symbol_length: usize) -> (Self, In<T>)
|
||||
{
|
||||
@ -53,7 +54,7 @@ where
|
||||
symbol_length,
|
||||
window_location: 0,
|
||||
window_center: symbol_length / 2,
|
||||
next_sample: symbol_length, // We assume that the first symbol is 1.5 windows into
|
||||
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),
|
||||
},
|
||||
@ -64,13 +65,14 @@ where
|
||||
|
||||
impl<'view, T> SyncBlock<'view> for EarlyLateGate<T>
|
||||
where
|
||||
T: Float + Sum + Clone + 'static,
|
||||
T: Float + Sum + Clone + 'static + Send + Sync + NumCast,
|
||||
{
|
||||
fn sync_work(state: Self::StateView, input: Self::Input) -> Option<Self::Output>
|
||||
{
|
||||
if state.window.len() < *state.symbol_length
|
||||
{
|
||||
state.window.push_back(input.0);
|
||||
*state.window_location += 1;
|
||||
return Some(input.0.into());
|
||||
}
|
||||
|
||||
@ -81,12 +83,8 @@ where
|
||||
|
||||
let sample = state.window[*state.window_center];
|
||||
let mut tag = None;
|
||||
if *state.window_location >= *state.next_sample
|
||||
if *state.window_location >= *state.next_sample as usize
|
||||
{
|
||||
let new_tag = Tag::default();
|
||||
new_tag.tag("elg_symbol", ());
|
||||
tag = Some(new_tag);
|
||||
|
||||
let early_index = *state.window_center - (0.25 * *state.symbol_length as f32) as usize;
|
||||
let late_index = *state.window_center + (0.25 * *state.symbol_length as f32) as usize;
|
||||
|
||||
@ -97,13 +95,16 @@ where
|
||||
let correction = state.loop_filter.next(error);
|
||||
|
||||
// Figure out next sample location
|
||||
*state.next_sample += (*state.symbol_length as isize
|
||||
+ correction.floor().to_isize().unwrap_or(0))
|
||||
.max(0) as usize;
|
||||
*state.next_sample +=
|
||||
(*state.symbol_length as f32 + correction.to_f32().unwrap()).max(0.);
|
||||
|
||||
// Turn everything back relative to current sample
|
||||
*state.next_sample -= *state.window_location;
|
||||
*state.next_sample -= *state.window_location as f32;
|
||||
*state.window_location = 0;
|
||||
|
||||
let new_tag = Tag::default();
|
||||
new_tag.tag("elg_symbol", error);
|
||||
tag = Some(new_tag);
|
||||
}
|
||||
|
||||
Some((sample, tag).into())
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod adapters;
|
||||
pub mod channels;
|
||||
pub mod iter;
|
||||
pub mod squelch;
|
||||
|
||||
@ -204,6 +204,55 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
#[sync_block(tagged)]
|
||||
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<'view, I, O, S, F> SyncBlock<'view> for ScanTagged<I, O, S, F>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
S: 'view,
|
||||
F: Fn(&mut S, Tagged<I>) -> Tagged<O> + 'view,
|
||||
{
|
||||
fn sync_work(state: Self::StateView, input: Self::Input) -> Option<Self::Output>
|
||||
{
|
||||
Some((*state.map)(state.state, input))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BlockIO)]
|
||||
pub struct Repeat<T: 'static>
|
||||
{
|
||||
@ -248,22 +297,24 @@ impl<T: Clone + 'static> Block for Repeat<T>
|
||||
{
|
||||
if self.remaining == 0 || self.current.is_none()
|
||||
{
|
||||
self.current = Some(reader.pop().unwrap().into());
|
||||
self.remaining = self.repetitions;
|
||||
if let Some(x) = reader.pop()
|
||||
{
|
||||
self.current = Some(x.into());
|
||||
self.remaining = self.repetitions;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BlockResult::Ok;
|
||||
}
|
||||
}
|
||||
|
||||
writer
|
||||
.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;
|
||||
|
||||
@ -51,7 +51,7 @@ impl<I: 'static> Block for RxSource<Receiver<I>, I>
|
||||
{
|
||||
if self
|
||||
.output
|
||||
.push_iter(self.input.iter().map(|x| (x, None).into()))
|
||||
.push_iter(self.input.try_iter().map(|x| (x, None).into()))
|
||||
{
|
||||
BlockResult::Ok
|
||||
}
|
||||
|
||||
79
oxydsp-dsp/src/blocks/utilities/squelch.rs
Normal file
79
oxydsp-dsp/src/blocks/utilities/squelch.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use std::{collections::VecDeque, iter::Sum};
|
||||
|
||||
use num::{Float, FromPrimitive, One, Zero, complex::ComplexFloat};
|
||||
use oxydsp_flowgraph::{
|
||||
BlockIO,
|
||||
block::{Block, BlockResult},
|
||||
io::{In, Out, PopIterable},
|
||||
};
|
||||
|
||||
use crate::filtering::fir::{Fir, FirFilter};
|
||||
|
||||
#[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 writer = self.output.write();
|
||||
for x in self.input.pop_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
|
||||
}
|
||||
}
|
||||
@ -30,6 +30,11 @@ impl<T> Nco<T>
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frequency(&self) -> DigitalFrequency
|
||||
{
|
||||
DigitalFrequency(self.d_phase)
|
||||
}
|
||||
|
||||
pub fn with_phase(frequency: DigitalFrequency, phase: Phase) -> Self
|
||||
{
|
||||
Self {
|
||||
|
||||
Reference in New Issue
Block a user