diff --git a/Cargo.lock b/Cargo.lock index 5956744..75f8d7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/example/Cargo.toml b/example/Cargo.toml index 7fafb3b..73cad67 100644 --- a/example/Cargo.toml +++ b/example/Cargo.toml @@ -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" diff --git a/example/mod.wav b/example/mod.wav index 2a6cc46..662e18f 100644 Binary files a/example/mod.wav and b/example/mod.wav differ diff --git a/example/out.dot b/example/out.dot index a311915..15d7de4 100644 --- a/example/out.dot +++ b/example/out.dot @@ -3,21 +3,23 @@ node [shape=record]; rankdir=TB; IterSource_0 [label="{ IterSource |{ output} }"]; -Map_1 [label="{ { input}| Map |{ output} }"]; -Repeat_2 [label="{ { input}| Repeat |{ output} }"]; -Nco_3 [label="{ { frequency}| Nco |{ output} }"]; -OscillatorSource_4 [label="{ OscillatorSource |{ output} }"]; -Multiplier_5 [label="{ { input_a| input_b}| Multiplier |{ output} }"]; -MapResultTagged_6 [label="{ { input}| MapResultTagged |{ output} }"]; -NullSink_7 [label="{ { input}| NullSink }"]; +FirFilter_1 [label="{ { input}| FirFilter |{ output} }"]; +Map_2 [label="{ { input}| Map |{ output} }"]; +Repeat_3 [label="{ { input}| Repeat |{ output} }"]; +Nco_4 [label="{ { frequency}| Nco |{ output} }"]; +OscillatorSource_5 [label="{ OscillatorSource |{ output} }"]; +Multiplier_6 [label="{ { input_a| input_b}| Multiplier |{ output} }"]; +MapResultTagged_7 [label="{ { input}| MapResultTagged |{ output} }"]; +NullSink_8 [label="{ { 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"]; -OscillatorSource_4:o0 -> Multiplier_5:i1 [label="num_complex::Complex"]; -Multiplier_5:o0 -> MapResultTagged_6:i0 [label="num_complex::Complex"]; -MapResultTagged_6:o0 -> NullSink_7:i0 [label="num_complex::Complex"]; + 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"]; +OscillatorSource_5:o0 -> Multiplier_6:i1 [label="num_complex::Complex"]; +Multiplier_6:o0 -> MapResultTagged_7:i0 [label="num_complex::Complex"]; +MapResultTagged_7:o0 -> NullSink_8:i0 [label="num_complex::Complex"]; } \ No newline at end of file diff --git a/example/src/main.rs b/example/src/main.rs index 73317db..f85953d 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -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 -{ - #[input] - input: In, +use crate::transmitter::Transmitter; - n: usize, -} - -impl Block for Printer -{ - 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 -// { -// fn sync_work(state: Self::StateView, input: Self::Input) -> Option -// { -// // 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, - - tk: String, - - n: usize, -} - -impl SourceTag -{ - pub fn new(tk: String) -> (Self, In) - { - 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 -// { -// *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 -// { -// type StateView = PrinterView<'view, T>; -// type Input = (); -// type Output = (); -// } -// } - -impl Printer -{ - pub fn new(input: In) -> 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::() - .map(|sample| (sample.unwrap() as f32) / (i16::MAX as f32)) - .collect::>(); - 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, Complex>::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::::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::(), - )); - - plot_ui.points( - Points::new( - "symbols", - output - .iter() - .enumerate() - .filter(|(_, (_, x))| *x) - .map(|(i, (s, _))| [i as f64, *s as f64]) - .collect::>(), - ) - .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::>(); - 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::::new(freq); - let (local_oscillator, lo) = OscillatorSource::::new(carrier.into()); - let (frontend, passband) = Multiplier::new(baseband, lo); - let (tx, rx) = mpsc::channel::>(); - 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::(), - )); - }); - 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] { diff --git a/example/src/receiver.rs b/example/src/receiver.rs new file mode 100644 index 0000000..a50ea4e --- /dev/null +++ b/example/src/receiver.rs @@ -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::() + .map(|sample| (sample.unwrap() as f32) / (i16::MAX as f32)) + .collect::>(); + 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 = sample / *state; + *state = sample; + angle.arg() * 14. + }); + let (sig_lowpass, arg) = + FirFilter::::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::>(), + 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::(), + ) + .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.))) +} diff --git a/example/src/transmitter.rs b/example/src/transmitter.rs new file mode 100644 index 0000000..60d6542 --- /dev/null +++ b/example/src/transmitter.rs @@ -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 +where + I: 'static, + O: IntoIterator + 'static, + O::IntoIter: FusedIterator, + F: Fn(I) -> O, +{ + #[input] + input: In, + + #[output] + output: Out, + + current_iter: Option, + map: F, +} + +impl FlatMap +where + I: 'static, + O: IntoIterator + 'static, + O::IntoIter: FusedIterator, + F: Fn(I) -> O, +{ + pub fn new(input: In, map: F) -> (Self, In) + { + let (output, port) = oxydsp_flowgraph::io::stream(); + ( + Self { + input, + output, + current_iter: None, + map, + }, + port, + ) + } +} + +impl Block for FlatMap +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>, + 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>) = sync_channel(128); + let (packet_rec, packets): (_, In>) = 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::::new(freq); + let (local_oscillator, lo) = OscillatorSource::::new(carrier.into()); + let (frontend, passband) = Multiplier::new(baseband, lo); + let (audio_tx, audio_rx) = mpsc::channel::>(); + 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) + { + let _ = self.packet_sender.send(data); + } +} diff --git a/oxydsp-dsp/src/blocks.rs b/oxydsp-dsp/src/blocks.rs index 84ce1ad..b22e196 100644 --- a/oxydsp-dsp/src/blocks.rs +++ b/oxydsp-dsp/src/blocks.rs @@ -1,4 +1,5 @@ pub mod filtering; +pub mod iq; pub mod math; pub mod synthesis; pub mod ted; diff --git a/oxydsp-dsp/src/blocks/iq.rs b/oxydsp-dsp/src/blocks/iq.rs new file mode 100644 index 0000000..24a7538 --- /dev/null +++ b/oxydsp-dsp/src/blocks/iq.rs @@ -0,0 +1 @@ +pub mod zero_if; diff --git a/oxydsp-dsp/src/blocks/iq/zero_if.rs b/oxydsp-dsp/src/blocks/iq/zero_if.rs new file mode 100644 index 0000000..e9f37b8 --- /dev/null +++ b/oxydsp-dsp/src/blocks/iq/zero_if.rs @@ -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 + 'static> +{ + #[input] + input: In, + + #[output] + output: Out>, + + local_oscillator: Nco, + filter: FirFilter, Complex, Complex>, +} + +impl ZeroIf +where + T: std::clone::Clone + num::Num + FftNum + From + 'static + num::Float, +{ + pub fn new(input: In, lo: Nco) -> (Self, In>) + { + 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>) + { + self.filter = FirFilter::new(fir); + } +} + +impl<'view, T> SyncBlock<'view> for ZeroIf +where + T: std::clone::Clone + num::Num + Float + From + 'static + num::Float, +{ + fn sync_work(state: Self::StateView, input: Self::Input) -> Option + { + // 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)) + } +} diff --git a/oxydsp-dsp/src/blocks/ted/early_late.rs b/oxydsp-dsp/src/blocks/ted/early_late.rs index b363b95..4f81e51 100644 --- a/oxydsp-dsp/src/blocks/ted/early_late.rs +++ b/oxydsp-dsp/src/blocks/ted/early_late.rs @@ -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 +pub struct EarlyLateGate { #[input] input: In, @@ -34,13 +35,13 @@ pub struct EarlyLateGate // 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, } impl EarlyLateGate where - T: Float + Sum + Clone + 'static, + T: Float + Sum + Clone + 'static + Send + Sync + NumCast, { pub fn new(input: In, loop_filter: Fir, symbol_length: usize) -> (Self, In) { @@ -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 where - T: Float + Sum + Clone + 'static, + T: Float + Sum + Clone + 'static + Send + Sync + NumCast, { fn sync_work(state: Self::StateView, input: Self::Input) -> Option { 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()) diff --git a/oxydsp-dsp/src/blocks/utilities.rs b/oxydsp-dsp/src/blocks/utilities.rs index 103f697..f346df0 100644 --- a/oxydsp-dsp/src/blocks/utilities.rs +++ b/oxydsp-dsp/src/blocks/utilities.rs @@ -1,3 +1,4 @@ pub mod adapters; pub mod channels; pub mod iter; +pub mod squelch; diff --git a/oxydsp-dsp/src/blocks/utilities/adapters.rs b/oxydsp-dsp/src/blocks/utilities/adapters.rs index beb95d4..5518a9a 100644 --- a/oxydsp-dsp/src/blocks/utilities/adapters.rs +++ b/oxydsp-dsp/src/blocks/utilities/adapters.rs @@ -204,6 +204,55 @@ where } } +#[derive(BlockIO)] +#[sync_block(tagged)] +pub struct ScanTagged +where + F: Fn(&mut S, Tagged) -> Tagged, +{ + #[input] + input: In, + + #[output] + output: Out, + + state: S, + + map: F, +} + +impl ScanTagged +where + F: Fn(&mut S, Tagged) -> Tagged, +{ + pub fn new(input: In, initial_state: S, map: F) -> (Self, In) + { + let (output, mapped) = stream(); + ( + Self { + input, + output, + state: initial_state, + map, + }, + mapped, + ) + } +} + +impl<'view, I, O, S, F> SyncBlock<'view> for ScanTagged +where + I: 'static, + O: 'static, + S: 'view, + F: Fn(&mut S, Tagged) -> Tagged + 'view, +{ + fn sync_work(state: Self::StateView, input: Self::Input) -> Option + { + Some((*state.map)(state.state, input)) + } +} + #[derive(BlockIO)] pub struct Repeat { @@ -248,22 +297,24 @@ impl Block for Repeat { 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; diff --git a/oxydsp-dsp/src/blocks/utilities/channels.rs b/oxydsp-dsp/src/blocks/utilities/channels.rs index 909a95f..578573d 100644 --- a/oxydsp-dsp/src/blocks/utilities/channels.rs +++ b/oxydsp-dsp/src/blocks/utilities/channels.rs @@ -51,7 +51,7 @@ impl Block for RxSource, 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 } diff --git a/oxydsp-dsp/src/blocks/utilities/squelch.rs b/oxydsp-dsp/src/blocks/utilities/squelch.rs new file mode 100644 index 0000000..10edb79 --- /dev/null +++ b/oxydsp-dsp/src/blocks/utilities/squelch.rs @@ -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 +where + T: ComplexFloat + 'static, + T::Real: Float + One + Zero + FromPrimitive + Sum + Clone, +{ + #[input] + input: In, + + #[output] + output: Out, + + trigger_level: T::Real, + + history: VecDeque, + sum: T::Real, + divider: T::Real, +} + +impl Squelch +where + T: ComplexFloat + 'static, + T::Real: Float + Sum + Clone + FromPrimitive, +{ + pub fn new(input: In, trigger_level: T::Real, mean_length: usize) -> (Self, In) + { + 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 Block for Squelch +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 + } +} diff --git a/oxydsp-dsp/src/synthesis/oscillator.rs b/oxydsp-dsp/src/synthesis/oscillator.rs index 7e5013b..be94261 100644 --- a/oxydsp-dsp/src/synthesis/oscillator.rs +++ b/oxydsp-dsp/src/synthesis/oscillator.rs @@ -30,6 +30,11 @@ impl Nco } } + pub fn frequency(&self) -> DigitalFrequency + { + DigitalFrequency(self.d_phase) + } + pub fn with_phase(frequency: DigitalFrequency, phase: Phase) -> Self { Self {