ELG Gate kind of working

This commit is contained in:
2025-10-02 15:01:04 +02:00
parent 6f01bbdc5d
commit 942cc1fdf0
9 changed files with 1342 additions and 177 deletions

244
Cargo.lock generated
View File

@ -127,6 +127,28 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "alsa"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43"
dependencies = [
"alsa-sys",
"bitflags 2.9.4",
"cfg-if",
"libc",
]
[[package]]
name = "alsa-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "android-activity"
version = "0.6.0"
@ -207,7 +229,7 @@ version = "0.38.0+1.3.281"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
dependencies = [
"libloading",
"libloading 0.8.9",
]
[[package]]
@ -690,6 +712,47 @@ dependencies = [
"libc",
]
[[package]]
name = "coreaudio-rs"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aae284fbaf7d27aa0e292f7677dfbe26503b0d555026f702940805a630eac17"
dependencies = [
"bitflags 1.3.2",
"libc",
"objc2-audio-toolbox",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
]
[[package]]
name = "cpal"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd307f43cc2a697e2d1f8bc7a1d824b5269e052209e28883e5bc04d095aaa3f"
dependencies = [
"alsa",
"coreaudio-rs",
"dasp_sample",
"jack",
"jni",
"js-sys",
"libc",
"mach2",
"ndk",
"ndk-context",
"num-derive",
"num-traits",
"objc2-audio-toolbox",
"objc2-core-audio",
"objc2-core-audio-types",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows 0.54.0",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
@ -717,6 +780,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 = "dirs"
version = "6.0.0"
@ -771,7 +840,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
"libloading",
"libloading 0.8.9",
]
[[package]]
@ -1313,7 +1382,7 @@ dependencies = [
"glutin_egl_sys",
"glutin_glx_sys",
"glutin_wgl_sys",
"libloading",
"libloading 0.8.9",
"objc2 0.6.2",
"objc2-app-kit 0.3.1",
"objc2-core-foundation",
@ -1642,6 +1711,33 @@ dependencies = [
"hashbrown 0.16.0",
]
[[package]]
name = "jack"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f70ca699f44c04a32d419fc9ed699aaea89657fc09014bf3fa238e91d13041b9"
dependencies = [
"bitflags 2.9.4",
"jack-sys",
"lazy_static",
"libc",
"log",
]
[[package]]
name = "jack-sys"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6013b7619b95a22b576dfb43296faa4ecbe40abbdb97dfd22ead520775fc86ab"
dependencies = [
"bitflags 1.3.2",
"lazy_static",
"libc",
"libloading 0.7.4",
"log",
"pkg-config",
]
[[package]]
name = "jni"
version = "0.21.1"
@ -1697,7 +1793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
dependencies = [
"libc",
"libloading",
"libloading 0.8.9",
"pkg-config",
]
@ -1719,6 +1815,16 @@ version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "libloading"
version = "0.8.9"
@ -1786,6 +1892,15 @@ version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "mach2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
dependencies = [
"libc",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
@ -1937,6 +2052,17 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[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-traits"
version = "0.2.19"
@ -2032,6 +2158,21 @@ dependencies = [
"objc2-foundation 0.3.1",
]
[[package]]
name = "objc2-audio-toolbox"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cbe18d879e20a4aea544f8befe38bcf52255eb63d3f23eca2842f3319e4c07"
dependencies = [
"bitflags 2.9.4",
"libc",
"objc2 0.6.2",
"objc2-core-audio",
"objc2-core-audio-types",
"objc2-core-foundation",
"objc2-foundation 0.3.1",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.2.2"
@ -2056,6 +2197,28 @@ dependencies = [
"objc2-foundation 0.2.2",
]
[[package]]
name = "objc2-core-audio"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca44961e888e19313b808f23497073e3f6b3c22bb485056674c8b49f3b025c82"
dependencies = [
"dispatch2",
"objc2 0.6.2",
"objc2-core-audio-types",
"objc2-core-foundation",
]
[[package]]
name = "objc2-core-audio-types"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f1cc99bb07ad2ddb6527ddf83db6a15271bb036b3eb94b801cd44fdc666ee1"
dependencies = [
"bitflags 2.9.4",
"objc2 0.6.2",
]
[[package]]
name = "objc2-core-data"
version = "0.2.2"
@ -2508,6 +2671,15 @@ dependencies = [
"zerovec",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "presser"
version = "0.3.1"
@ -2587,6 +2759,35 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.3",
]
[[package]]
name = "range-alloc"
version = "0.1.4"
@ -2603,10 +2804,12 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
name = "rdsp"
version = "0.1.0"
dependencies = [
"cpal",
"eframe",
"egui_plot",
"hound",
"plotters",
"rand",
]
[[package]]
@ -3550,7 +3753,7 @@ dependencies = [
"js-sys",
"khronos-egl",
"libc",
"libloading",
"libloading 0.8.9",
"log",
"metal",
"naga",
@ -3617,6 +3820,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
dependencies = [
"windows-core 0.54.0",
"windows-targets 0.52.6",
]
[[package]]
name = "windows"
version = "0.58.0"
@ -3649,6 +3862,16 @@ dependencies = [
"windows-core 0.61.2",
]
[[package]]
name = "windows-core"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
dependencies = [
"windows-result 0.1.2",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.58.0"
@ -3765,6 +3988,15 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-result"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.2.0"
@ -4162,7 +4394,7 @@ dependencies = [
"as-raw-xcb-connection",
"gethostname",
"libc",
"libloading",
"libloading 0.8.9",
"once_cell",
"rustix 1.1.2",
"x11rb-protocol",

View File

@ -4,7 +4,9 @@ version = "0.1.0"
edition = "2024"
[dependencies]
cpal = { version = "0.16.0", features = ["jack"] }
eframe = "0.32.3"
egui_plot = "0.33.0"
hound = "3.5.1"
plotters = "0.3.7"
rand = "0.9.2"

424
main.rs.mt Normal file
View File

@ -0,0 +1,424 @@
#![allow(dead_code)]
use std::{
f32::consts::PI,
fs::File,
i16,
io::{Read, Write},
ops::{Add, Div, Mul, Sub},
};
mod bfsk;
mod complex;
pub mod fft;
mod filtering;
mod iq;
mod nco;
mod signal;
mod units;
mod windows;
use bfsk::BFSKMod;
use complex::Complex;
use complex::Complex32;
use fft::DFTAlgorithm;
use nco::Nco;
use eframe::egui::{self, Color32, Context, debug_text::print};
use egui_plot::{self, Bar, BarChart, Legend, Line, LineStyle, Plot, PlotPoints, VLine};
use plotters::style::Color;
use crate::{
bfsk::BFSKDem,
fft::{FFT, dft::NaiveDFT},
filtering::{
fir::FIRFilter,
impulse_response::design::{self, frequency_response, ir_from_transfer_function},
},
iq::IQSampler,
units::frequency::{self, hz_to_rad_per_sample},
};
// Utilities
fn map<T>(input: T, in_min: T, in_max: T, out_min: T, out_max: T) -> T
where
T: Clone + Add<Output = T> + Mul<Output = T> + Sub<Output = T> + Div<Output = T>,
{
((input - in_min.clone()) / (in_max - in_min)) * (out_max - out_min.clone()) + out_min
}
fn main() {
modulate();
println!("Demodulating");
demodulate();
return;
let native_options = eframe::NativeOptions::default();
let _ = eframe::run_native(
"Egui",
native_options,
Box::new(|cc| Ok(Box::new(EguiApp::new(cc)))),
);
}
#[derive(Default)]
struct EguiApp {
samples: Vec<f32>,
iq: Vec<Complex32>,
dem: Vec<f32>,
vlines: Vec<f32>,
elg_sampling: Vec<f32>,
eye_diagram: Vec<Vec<f32>>,
}
const BAUD_RATE: u32 = 3200;
impl EguiApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap();
let samples = reader.samples::<i16>();
let sample_count = 10_000;
let input_test = samples
.take(sample_count)
.map(|x| x.unwrap() as f32 / i16::MAX as f32)
.collect::<Vec<_>>();
// Modulation parameters
let frequency = 1700.;
let deviation = 500.;
// Data parameters
let sample_rate = 48000;
let baud_rate = BAUD_RATE;
let sample_per_symbol = sample_rate / baud_rate;
let vlines = (0..sample_count)
.filter(|i| i % sample_per_symbol as usize == 0)
.map(|x| x as f32 / sample_count as f32)
.collect();
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
let iq = input_test
.iter()
.map(|x| iq_sampler.sample(*x))
.collect::<Vec<_>>();
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
let corellator_pos = (0..(sample_per_symbol * 1))
.map(|_| {
nco_pos.step();
nco_pos.cexp()
})
.collect::<Vec<_>>();
let corellator_neg = (0..(sample_per_symbol * 1))
.map(|_| {
nco_neg.step();
nco_neg.cexp()
})
.collect::<Vec<_>>();
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
let mut dem = vec![];
let mut loop_filter = FIRFilter::new(&[Complex32::new(1., 0.); 1]);
for x in &iq {
let pos = matched_filter_pos.next(x.clone());
let neg = matched_filter_neg.next(*x);
dem.push(
loop_filter
.next(Complex32::new(pos.mag() - neg.mag(), 0.))
.re,
);
}
// Symbol recovery
let mut sample_ids = vec![];
let delta = 0.5;
let alpha = 0.01;
let mut current_sps = sample_per_symbol as f32;
let mut current_position = current_sps / 2.;
let mut eye_diagram = vec![];
while current_position < dem.len() as f32 {
// Sample before after
let early_id = (current_position - (delta * current_sps)).max(0.).floor() as u32;
let late_id = (current_position + (delta * current_sps)).max(0.).floor() as u32;
if late_id as usize >= dem.len() {
break;
}
let early = dem[early_id as usize];
let late = dem[late_id as usize];
let error = early * early - late * late;
current_sps -= alpha * error;
sample_ids.push(current_position);
let eye = ((current_position - current_sps).max(0.) as usize
..(current_position + current_sps).min(sample_count as f32) as usize)
.map(|i| dem[i])
.collect();
eye_diagram.push(eye);
current_position += current_sps;
}
let elg_sampling = sample_ids
.iter()
.map(|x| *x / sample_count as f32)
.collect();
Self {
samples: input_test.clone(),
iq,
dem,
vlines,
elg_sampling,
eye_diagram,
}
}
}
impl eframe::App for EguiApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Hello World!");
Plot::new("Fourrier transform")
.legend(Legend::default())
.show(ui, |plot_ui| {
for (i, eye) in self.eye_diagram.iter().enumerate().skip(1) {
plot_ui.line(
Line::new(
"eye",
eye.iter()
.enumerate()
.map(|(i, x)| [i as f64 / eye.len() as f64, *x as f64])
.collect::<Vec<_>>(),
)
.width(1.)
.color(Color32::RED),
);
}
self.vlines.iter().for_each(|l| {
plot_ui.vline(
VLine::new("Boundaries", *l as f64)
.color(Color32::LIGHT_BLUE)
.width(0.5)
.style(LineStyle::dashed_dense()),
);
});
self.elg_sampling.iter().for_each(|l| {
plot_ui.vline(
VLine::new("ELG", *l as f64)
.color(Color32::RED)
.width(0.5)
.style(LineStyle::dotted_dense()),
);
});
plot_ui.line(
Line::new(
"Passband",
self.samples
.iter()
.enumerate()
.map(|(i, x)| [i as f64 / self.samples.len() as f64, *x as f64])
.collect::<Vec<_>>(),
)
.width(2.),
);
plot_ui.line(
Line::new(
"Demodulated",
self.dem
.iter()
.enumerate()
.map(|(i, x)| [i as f64 / self.iq.len() as f64, *x as f64])
.collect::<Vec<_>>(),
)
.width(2.),
);
})
});
}
}
fn demodulate() {
let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap();
let samples = reader.samples::<i16>();
// Modulation parameters
let frequency = 1700.;
let deviation = 500.;
// Data parameters
let sample_rate = 48000;
let baud_rate = BAUD_RATE;
let sample_per_symbol = sample_rate / baud_rate;
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
let iq = samples
.map(|x| iq_sampler.sample(x.unwrap() as f32 / i16::MAX as f32))
.collect::<Vec<_>>();
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
let corellator_pos = (0..(sample_per_symbol * 1))
.map(|_| {
nco_pos.step();
nco_pos.cexp()
})
.collect::<Vec<_>>();
let corellator_neg = (0..(sample_per_symbol * 1))
.map(|_| {
nco_neg.step();
nco_neg.cexp()
})
.collect::<Vec<_>>();
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
let mut dem = vec![];
for x in &iq {
let pos = matched_filter_pos.next(x.clone());
let neg = matched_filter_neg.next(*x);
dem.push(pos.mag() - neg.mag());
}
// Symbol recovery
let mut bits = vec![];
let delta = 0.5;
let alpha = 0.01;
let mut current_sps = sample_per_symbol as f32;
let mut current_position = current_sps / 2.;
while current_position < dem.len() as f32 {
// Sample before after
let early_id = (current_position - (delta * current_sps)).max(0.).floor() as u32;
let late_id = (current_position + (delta * current_sps)).max(0.).floor() as u32;
if late_id as usize >= dem.len() {
break;
}
let early = dem[early_id as usize];
let late = dem[late_id as usize];
let error = early * early - late * late;
current_sps -= alpha * error;
bits.push(dem[current_position.floor() as usize] > 0.);
current_position += current_sps;
}
//assert!(bits.len() % 8 == 0);
let mut out_file = File::create("out.txt").unwrap();
let mut strip = 0;
let bit_slice = bits.as_slice();
for i in 0..100 {
let byte = bits_to_byte(&bit_slice[(i as usize)..(i as usize + 8)]);
if byte == 0b01010111u8 {
strip = i + 8;
}
}
for i in 0..strip {
bits.remove(i as usize);
}
for x in bits.chunks(8) {
if x.len() != 8 {
break;
}
out_file.write_all(&[bits_to_byte(x)]).unwrap();
}
}
fn modulate() {
// Modulation parameters
let frequency = 1700.;
let deviation = 500.;
// Data parameter
let sample_rate = 48000;
let baud_rate = BAUD_RATE;
// File to modulate
let f = File::open("s.txt").unwrap();
let mut bitstream = std::iter::repeat_n(0b01010101u8, 1)
.chain(std::iter::repeat_n(0b01010111u8, 1))
.chain(f.bytes().map(|x| x.unwrap()))
.chain(std::iter::repeat_n(0u8, 1))
.flat_map(byte_to_bits);
let mut modulator = BFSKMod::new(
sample_rate / baud_rate,
units::frequency::hz_to_rad_per_sample(deviation, sample_rate as f32),
&mut bitstream,
);
let mut lo = Nco::new(units::frequency::hz_to_rad_per_sample(
frequency,
sample_rate as f32,
));
let spec = hound::WavSpec {
channels: 1,
sample_rate,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};
let mut writer = hound::WavWriter::create("audio/modulated.wav", spec).unwrap();
for (s, up) in modulator.zip(lo) {
let sample = (s * up).re; // Project to I coords
let amplitude = i16::MAX as f32;
writer.write_sample((sample * amplitude) as i16).unwrap();
}
writer.finalize().unwrap();
}
fn byte_to_bits(byte: u8) -> Vec<bool> {
vec![
byte & 1 == 1,
(byte >> 1) & 1 == 1,
(byte >> 2) & 1 == 1,
(byte >> 3) & 1 == 1,
(byte >> 4) & 1 == 1,
(byte >> 5) & 1 == 1,
(byte >> 6) & 1 == 1,
(byte >> 7) & 1 == 1,
]
}
/*
fn bits_to_byte(bits: &[bool]) -> u8 {
bits[7] as u8 |
bits[6] as u8 >> 1 |
bits[5] as u8 >> 2 |
bits[4] as u8 >> 3 |
bits[3] as u8 >> 4 |
bits[2] as u8 >> 5 |
bits[1] as u8 >> 6 |
bits[0] as u8 >> 7
}
*/
fn bits_to_byte(bits: &[bool]) -> u8 {
bits[0] as u8
| (bits[1] as u8) << 1
| (bits[2] as u8) << 2
| (bits[3] as u8) << 3
| (bits[4] as u8) << 4
| (bits[5] as u8) << 5
| (bits[6] as u8) << 6
| (bits[7] as u8) << 7
}

View File

@ -1,8 +1,3 @@
!<21>hbidi Toilet[1],[2] est une web-série[3] machinima, créée par Alexey Gerasimov et diffusée sur sa chaîne YouTube DaFuq!?Boom! Produite à l'aide de Source Filmmaker, la série suit une guerre fictive entre des toilettes à tête humaine et des personnages humanoïdes dotés d'appareils électroniques à la place de la tête.
Après la publication du premier court métrage en février 2023, Skibidi Toilet devient un mème Internet viral sur divers réseaux sociaux, en particulier au sein de la génération Alpha. Les critiques ont vu dans cette série la première incursion de la génération Alpha dans la culture Internet, en concurrence avec la génération Z, plus âgée.
Synopsis
Skibidi Toilet raconte les événements d'une guerre fictive entre des hommes à tête de caméra, de haut-parleur ou de télévision, et les Skibidi Toilets, une race extraterrestre prenant lapparence de toilettes avec des têtes d'hommes ou de femmes et n'ayant que comme langage le « Skibidi ». Dirigés par le G-Toilet (personnage inspiré du G-Man dans la série de jeux vidéos Half-Life[4]), ils ont comme ambition de conquérir la Terre, en éliminant toute opposition[5],[6]. G-Toilet, qui est sous les ordres des Astro-Toilets, sa race d'origine qui se trouve dans l'espace, doit conquérir la terre avec l'armée qu'il a créée sur Terre, finira par perdre sa confiance aux Astros dû à sa défaite à l'épisode 57 et à sa dispute à l'épisode 60 avec les deux soldats d'élite des Astros. À partir de l'épisode 74, les Astros ont commencé à envahir la Terre. Les Skibidi Toilets et l'alliance n'ont d'autre choix que de s'allier pour vaincre les Astros.
<EFBFBD><EFBFBD>ibidi Toilet[1],[2] est une web-série[3] machinima, créée par Alexey Gerasimov et diffusée sur sa chaîne YouTube DaFuq!?Boom! Produite à l'aide de Source Filmmaker, la série suit une guerre fictive entre des toilettes à tête humaine et des personnages humanoïdes dotés d'appareils électroniques à la place de la tête.
<10> 8<>a<EFBFBD>9<10>0<10>:1<><31><EFBFBD>0<EFBFBD><30>77<10>:8<><38><EFBFBD><EFBFBD>29<32><39><EFBFBD>:9:<3A><><EFBFBD>T:<3A><><EFBFBD>2<EFBFBD>27<10><>T;<3B><>29<18><16><><EFBFBD>4<EFBFBD>4<EFBFBD>4<10><>4<EFBFBD>2:<10>2<EFBFBD><32>27:<3A>:7<><37>aԶ2<D4B6>$7<>29<32>2:<10>4<EFBFBD>06<30><36>:9<10>4<EFBFBD>2<EFBFBD>9<10><>Թ<EFBFBD><D4B9>:<<3C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:<<16>27<10>09<30><39><EFBFBD>:<3A><>29<32><39>:<3A><><EFBFBD>47<10>2<10>0<EFBFBD><30><EFBFBD>T<EFBFBD><54>T<EFBFBD>0<EFBFBD><30>77<37> 68<36>0<10><>9<EFBFBD>1<EFBFBD>4<EFBFBD><34><EFBFBD><EFBFBD><EFBFBD>9<EFBFBD>77:<10>:<10>0<EFBFBD>9<EFBFBD><39>2:<3A>2<EFBFBD><32><EFBFBD>T<EFBFBD><54>2<10>08<><38><EFBFBD><EFBFBD>aT<61>2<EFBFBD>4<EFBFBD><34>:<3A><><EFBFBD>77<10>2<10>0<EFBFBD><30><EFBFBD>T<EFBFBD><54>T<EFBFBD>0<EFBFBD><30>77<37> 68<36>0<10>0<EFBFBD>9<10>0<EFBFBD><30>:6<>:<3A>2<EFBFBD>$7<>29<32>2:<16>27<32><37>7<EFBFBD><37>:9<>2<EFBFBD><32>2<EFBFBD>0<EFBFBD><30>1<10>0<EFBFBD><30><EFBFBD>T<EFBFBD><54>T<EFBFBD>0<EFBFBD><30>77-8<><38>9<EFBFBD><61><D1B3>2<05><><<3C>7<EFBFBD><37><EFBFBD>9<EFBFBD><39><EFBFBD>4<EFBFBD>4<EFBFBD>4<10><>4<EFBFBD>2:<10><><EFBFBD>77<37>2<10><>9<EFBFBD><39>T<EFBFBD><54>T<EFBFBD><54><EFBFBD>27<32>9<10><>:<3A>2<EFBFBD><32><EFBFBD>29<32>2<10><>1<EFBFBD>4<EFBFBD>2<EFBFBD>27:<3A>2<10><>9<10><><EFBFBD><EFBFBD><EFBFBD>9<EFBFBD>aP<10>aU<61>2<10>2<EFBFBD><32><EFBFBD><EFBFBD><EFBFBD>T<EFBFBD>0<10>2<10><>:<3A><16>09<30><39>:9<><39>:<10>2<10><>T<EFBFBD><54>T<EFBFBD><54><EFBFBD><EFBFBD>77<16>2:<10><>9<EFBFBD><39><EFBFBD>4<EFBFBD>4<EFBFBD>4<10><>4<EFBFBD>2<EFBFBD>9<16>:<3A>2<10><><EFBFBD>2<EFBFBD>2<:<3A>0<EFBFBD>29<32><39>9:<3A>28<>2<EFBFBD>07:6q<36><71>08<30>0<EFBFBD>2<EFBFBD><32>2<10>2<10><>4<EFBFBD>2:<3A><>9<EFBFBD>0<EFBFBD><30>1<10><>9<10>aU<61><55>9<10><13><><EFBFBD><EFBFBD><EFBFBD>9<EFBFBD><39>:<10>2<10><><EFBFBD><EFBFBD><EFBFBD>9<EFBFBD>2:<10><><EFBFBD><EFBFBD>07:<3A><><EFBFBD>2<EFBFBD><32><EFBFBD><EFBFBD><EFBFBD>2<10>0<EFBFBD><30><EFBFBD><EFBFBD>2<10>2<10>U<EFBFBD><55><EFBFBD>4<EFBFBD>4<EFBFBD>4<10>]<10>4<EFBFBD><34><EFBFBD><EFBFBD><EFBFBD>9<10>09<10>2<EFBFBD><32><16><>4<EFBFBD>2:<14>2<EFBFBD><32>77<37><37><EFBFBD>2<EFBFBD>4<EFBFBD>9<EFBFBD>4<EFBFBD><34>T<10>:<3A><><EFBFBD><EFBFBD>07<10>0<EFBFBD>9<10>0<EFBFBD><30><EFBFBD>T<EFBFBD><54>2<10>2<10><>:<<10>4<EFBFBD><34>Է9<10>06<30><16>4<EFBFBD><34>-<2D><><16>4<EFBFBD>9<EFBFBD>77:<3A><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD><32>6<EFBFBD>4<EFBFBD><34>77<10>2<EFBFBD><32>7<EFBFBD><37><EFBFBD><EFBFBD>T<EFBFBD>49<10>0<10>29<32>2<16>27<32><37>T<EFBFBD><54><EFBFBD>4<EFBFBD>07:<10><>:<3A>2<EFBFBD>78<37><38><EFBFBD>4<EFBFBD><34>7<EFBFBD><37><EFBFBD>.<2E>-<2D>.<17><><16><>4<EFBFBD>2:<16><><EFBFBD>4<EFBFBD><34>9:<3A><><EFBFBD><EFBFBD>9<10><>9<EFBFBD>792<39><32>9<10><>9<EFBFBD><39>9:<3A><><16><>4<EFBFBD>2<EFBFBD>9<16><>0<10><><EFBFBD>2<10><>7<EFBFBD><37><EFBFBD>4<EFBFBD>2<EFBFBD><32><EFBFBD>4<EFBFBD><34>2:<3A><>:<3A>2<10>0<EFBFBD>9<10><><EFBFBD>9<EFBFBD><39><EFBFBD>2<10><>4:<3A><>7<EFBFBD><37><EFBFBD><EFBFBD>T<EFBFBD>49<10>0<10>29<32>2<EFBFBD>0<EFBFBD><30>1<10><>0<EFBFBD><30><EFBFBD><EFBFBD>2<EFBFBD><32><EFBFBD><EFBFBD>46<34>0<EFBFBD>1<EFBFBD><31><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD><32>:9<10>29<32>2<10>4<EFBFBD>4<EFBFBD>0<10>09<10>292<39>2<EFBFBD><32>0<EFBFBD><30>77<37><37>0<EFBFBD><30>2<EFBFBD><32>:<<3C><>9:<3A><>9<10><>]<5D>aP<61><50>0<10><>T<EFBFBD><54>4<EFBFBD>2<EFBFBD>aP<10><><EFBFBD>T<EFBFBD><54><EFBFBD>7<EFBFBD>2<EFBFBD><32><1B>2:<3A>aP<61><50>0<10><>9<EFBFBD>:<3A>2<EFBFBD>aP<10><><EFBFBD>T<EFBFBD><54><EFBFBD>7<EFBFBD>2<18>0<EFBFBD><30>1<10><>9<10><>:<<3C><>76<37>0<EFBFBD>9<10><><EFBFBD>T<EFBFBD>4<EFBFBD>2<10><>9<EFBFBD><39>9:<3A><>9<17>a@<10>09<30>49<10>2<10><><EFBFBD>T<EFBFBD><54><EFBFBD>7<EFBFBD>2<EFBFBD><10><>9<EFBFBD><39>9:<3A><>9<EFBFBD>77:<3A><><EFBFBD><EFBFBD><EFBFBD>2<EFBFBD><32><EFBFBD>T<EFBFBD>aP<61>27<32>0<EFBFBD>49<10>0<10>29<32>2<10><>9<EFBFBD><39><EFBFBD>4<EFBFBD>4<EFBFBD>4<10><>4<EFBFBD>2<EFBFBD>9<EFBFBD>2:<10><>06<30><36>0<EFBFBD><30>2<10><>77:<10><><EFBFBD>::<3A>2<EFBFBD>1<EFBFBD><31>4<<3C><><EFBFBD>2<10>2<EFBFBD><32><EFBFBD>06<30><36>29<10><>:9<10><>4<EFBFBD>1<EFBFBD>2<10><>9<EFBFBD><39>9:<3A><>9

1
s.txt
View File

@ -1,6 +1,5 @@
Skibidi Toilet[1],[2] est une web-série[3] machinima, créée par Alexey Gerasimov et diffusée sur sa chaîne YouTube DaFuq!?Boom! Produite à l'aide de Source Filmmaker, la série suit une guerre fictive entre des toilettes à tête humaine et des personnages humanoïdes dotés d'appareils électroniques à la place de la tête.
Après la publication du premier court métrage en février 2023, Skibidi Toilet devient un mème Internet viral sur divers réseaux sociaux, en particulier au sein de la génération Alpha. Les critiques ont vu dans cette série la première incursion de la génération Alpha dans la culture Internet, en concurrence avec la génération Z, plus âgée.
Synopsis

View File

@ -42,4 +42,8 @@ impl FIRFilter {
.sum::<Complex32>()
/ self.normalization
}
pub fn next_real(&mut self, next: f32) -> f32 {
self.next(Complex32::new(next, 0.)).re
}
}

View File

@ -1,11 +1,18 @@
#![allow(dead_code)]
use std::{
collections::VecDeque,
f32::consts::PI,
fs::File,
i16,
io::{Read, Write},
ops::{Add, Div, Mul, Sub},
os::unix::thread,
sync::{
Arc,
mpsc::{self, Receiver, Sender, TryRecvError, sync_channel},
},
time::Duration,
};
mod bfsk;
@ -14,18 +21,24 @@ pub mod fft;
mod filtering;
mod iq;
mod nco;
mod signal;
mod units;
mod windows;
use bfsk::BFSKMod;
use complex::Complex;
use complex::Complex32;
use cpal::{
SampleRate,
traits::{DeviceTrait, HostTrait, StreamTrait},
};
use fft::DFTAlgorithm;
use nco::Nco;
use eframe::egui::{self, Color32, Context, debug_text::print};
use eframe::egui::{self, Color32, Context, Vec2b, debug_text::print};
use egui_plot::{self, Bar, BarChart, Legend, Line, LineStyle, Plot, PlotPoints, VLine};
use plotters::style::Color;
use rand::Rng;
use crate::{
bfsk::BFSKDem,
@ -45,138 +58,184 @@ where
{
((input - in_min.clone()) / (in_max - in_min)) * (out_max - out_min.clone()) + out_min
}
const BAUD_RATE: u32 = 1200;
fn main() {
modulate();
println!("Demodulating");
demodulate();
//demodulate();
//return;
// Set up CPAL
let host = cpal::default_host();
let device = host
.default_input_device()
.expect("No input device available");
let mut config = device
.supported_input_configs()
.unwrap()
.next()
.unwrap()
.with_sample_rate(SampleRate(48000));
// Channel to move samples from callback to main thread
let (tx, rx) = sync_channel::<f32>(1024);
// Build input stream
let stream = device
.build_input_stream(
&config.into(),
move |data: &[i16], _| {
for x in data.iter() {
let _ = tx.send(*x as f32 / i16::MAX as f32); // non-blocking send
}
},
move |err| eprintln!("Stream error: {}", err),
None,
)
.unwrap();
stream.play().unwrap();
let (eye_tx, eye_rx) = mpsc::channel::<Vec<f32>>();
let (ctx_tx, ctx_rx) = mpsc::channel::<Arc<egui::Context>>();
std::thread::spawn(move || demodulator(rx, eye_tx, ctx_rx));
/*
std::thread::spawn(move || {
let spec = hound::WavSpec {
channels: 1,
sample_rate: 48000,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};
let mut writer = hound::WavWriter::create("audio/noised.wav", spec).unwrap();
let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap();
let mut rand = rand::rng();
let samples = reader.samples::<i16>();
for x in samples {
let noise = rand.random::<f32>() * 2. - 1.;
let sample = x.unwrap() as f32 / i16::MAX as f32 + noise * 1.;
let _ = tx.send(sample);
writer
.write_sample((sample * i16::MAX as f32) as i16)
.unwrap();
std::thread::sleep(Duration::from_micros(21));
}
writer.finalize().unwrap();
});
*/
return;
let native_options = eframe::NativeOptions::default();
let _ = eframe::run_native(
"Egui",
native_options,
Box::new(|cc| Ok(Box::new(EguiApp::new(cc)))),
Box::new(|cc| Ok(Box::new(EguiApp::new(cc, eye_rx, ctx_tx)))),
);
}
#[derive(Default)]
struct EguiApp {
samples: Vec<f32>,
fn demodulator(
rx: Receiver<f32>,
eye_sender: Sender<Vec<f32>>,
ctx_rx: Receiver<Arc<egui::Context>>,
) {
// Wait for egui context to request redraw
let ctx = ctx_rx.recv().unwrap();
iq: Vec<Complex32>,
dem: Vec<f32>,
vlines: Vec<f32>,
elg_sampling: Vec<f32>,
eye_diagram: Vec<Vec<f32>>,
}
const BAUD_RATE: u32 = 3200;
// Modulation parameters
let frequency = 1700.;
let deviation = 500.;
impl EguiApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap();
let samples = reader.samples::<i16>();
let sample_count = 10_000;
let input_test = samples
.take(sample_count)
.map(|x| x.unwrap() as f32 / i16::MAX as f32)
.collect::<Vec<_>>();
// Data parameters
let sample_rate = 48000;
let baud_rate = BAUD_RATE;
let sample_per_symbol = sample_rate / baud_rate;
// Modulation parameters
let frequency = 1700.;
let deviation = 500.;
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
// Data parameters
let sample_rate = 48000;
let baud_rate = BAUD_RATE;
let sample_per_symbol = sample_rate / baud_rate;
let vlines = (0..sample_count)
.filter(|i| i % sample_per_symbol as usize == 0)
.map(|x| x as f32 / sample_count as f32)
.collect();
// Corellators
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
let corellator_pos = (0..sample_per_symbol)
.map(|_| {
nco_pos.step();
nco_pos.cexp()
})
.collect::<Vec<_>>();
let corellator_neg = (0..sample_per_symbol)
.map(|_| {
nco_neg.step();
nco_neg.cexp()
})
.collect::<Vec<_>>();
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
let iq = input_test
.iter()
.map(|x| iq_sampler.sample(*x))
.collect::<Vec<_>>();
let mut elg_buffer = VecDeque::new();
let mut sps = sample_per_symbol as f32;
let delta = 0.3;
let loop_p = 0.1;
let loop_i = 0.3;
let mut loop_filter = FIRFilter::new(&[Complex32::new(1., 0.); 40]);
let mut matched_filter = FIRFilter::new(&[Complex32::new(1., 0.); 70]);
let mut current_position = 0.;
let mut next_sample = (sps as f32) / 2.;
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
let corellator_pos = (0..(sample_per_symbol * 1))
.map(|_| {
nco_pos.step();
nco_pos.cexp()
})
.collect::<Vec<_>>();
let corellator_neg = (0..(sample_per_symbol * 1))
.map(|_| {
nco_neg.step();
nco_neg.cexp()
})
.collect::<Vec<_>>();
//let mut dem = vec![];
// Timing recovery
while let Ok(real_sample) = rx.recv() {
let iq = iq_sampler.sample(real_sample);
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
// Perform corellation
let neg_energy = matched_filter_neg.next(iq);
let pos_energy = matched_filter_pos.next(iq);
let mut dem = vec![];
let matched = matched_filter.next_real(pos_energy.mag() - neg_energy.mag());
//dem.push(matched);
elg_buffer.push_front(matched);
current_position += 1.;
if current_position >= next_sample + sps / 2. {
// Compute current error
let early_id = (((sps / 2.) + sps * delta) as usize).min(elg_buffer.len() - 1);
let late_id = (((sps / 2.) - sps * delta) as usize).max(0);
let error =
elg_buffer[(sps / 2.) as usize] * (elg_buffer[early_id] - elg_buffer[late_id]);
let mut loop_filter = FIRFilter::new(&[Complex32::new(1., 0.); 1]);
for x in &iq {
let pos = matched_filter_pos.next(x.clone());
let neg = matched_filter_neg.next(*x);
dem.push(
loop_filter
.next(Complex32::new(pos.mag() - neg.mag(), 0.))
.re,
);
}
// Symbol recovery
let mut sample_ids = vec![];
let delta = 0.5;
let alpha = 0.01;
let mut current_sps = sample_per_symbol as f32;
let mut current_position = current_sps / 2.;
let mut eye_diagram = vec![];
while current_position < dem.len() as f32 {
// Sample before after
let early_id = (current_position - (delta * current_sps)).max(0.).floor() as u32;
let late_id = (current_position + (delta * current_sps)).max(0.).floor() as u32;
if late_id as usize >= dem.len() {
next_sample +=
sps - loop_p * error - loop_i * loop_filter.next(Complex32::new(error, 0.)).re;
while elg_buffer.len() > sps as usize {
elg_buffer.pop_back();
}
let _ = eye_sender.send(Vec::from(elg_buffer.clone()));
//elg_buffer.clear();
ctx.request_repaint();
/*
if dem.len() > 10_000 {
let _ = eye_sender.send(dem.clone());
break;
}
let early = dem[early_id as usize];
let late = dem[late_id as usize];
let error = early * early - late * late;
current_sps -= alpha * error;
sample_ids.push(current_position);
let eye = ((current_position - current_sps).max(0.) as usize
..(current_position + current_sps).min(sample_count as f32) as usize)
.map(|i| dem[i])
.collect();
eye_diagram.push(eye);
current_position += current_sps;
*/
}
}
}
let elg_sampling = sample_ids
.iter()
.map(|x| *x / sample_count as f32)
.collect();
//#[derive(Default)]
struct EguiApp {
eye_rx: Receiver<Vec<f32>>,
eyes: VecDeque<Vec<f32>>,
}
impl EguiApp {
fn new(
cc: &eframe::CreationContext<'_>,
eye_rx: Receiver<Vec<f32>>,
ctx_tx: Sender<Arc<egui::Context>>,
) -> Self {
ctx_tx.send(Arc::new(cc.egui_ctx.clone())).unwrap();
Self {
samples: input_test.clone(),
iq,
dem,
vlines,
elg_sampling,
eye_diagram,
eye_rx,
eyes: VecDeque::new(),
}
}
}
@ -184,65 +243,31 @@ impl EguiApp {
impl eframe::App for EguiApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Hello World!");
Plot::new("Fourrier transform")
let max_eyes = 100;
while let Ok(eye) = self.eye_rx.try_recv() {
self.eyes.push_back(eye);
}
while self.eyes.len() > max_eyes {
self.eyes.pop_front();
}
Plot::new("Eye")
.legend(Legend::default())
.show(ui, |plot_ui| {
for (i, eye) in self.eye_diagram.iter().enumerate().skip(1) {
plot_ui.line(
Line::new(
"eye",
eye.iter()
.enumerate()
.map(|(i, x)| [i as f64 / eye.len() as f64, *x as f64])
.collect::<Vec<_>>(),
)
.width(1.)
.color(Color32::RED),
);
//plot_ui.set_auto_bounds(Vec2b { x: false, y: false });
for eye in self.eyes.iter() {
let line = Line::new(
"Eye",
eye.iter()
.enumerate()
.map(|(i, x)| [i as f64, *x as f64])
.collect::<Vec<_>>(),
)
.color(Color32::LIGHT_GREEN);
plot_ui.line(line);
}
self.vlines.iter().for_each(|l| {
plot_ui.vline(
VLine::new("Boundaries", *l as f64)
.color(Color32::LIGHT_BLUE)
.width(0.5)
.style(LineStyle::dashed_dense()),
);
});
self.elg_sampling.iter().for_each(|l| {
plot_ui.vline(
VLine::new("ELG", *l as f64)
.color(Color32::RED)
.width(0.5)
.style(LineStyle::dotted_dense()),
);
});
plot_ui.line(
Line::new(
"Passband",
self.samples
.iter()
.enumerate()
.map(|(i, x)| [i as f64 / self.samples.len() as f64, *x as f64])
.collect::<Vec<_>>(),
)
.width(2.),
);
plot_ui.line(
Line::new(
"Demodulated",
self.dem
.iter()
.enumerate()
.map(|(i, x)| [i as f64 / self.iq.len() as f64, *x as f64])
.collect::<Vec<_>>(),
)
.width(2.),
);
})
});
}

484
src/main.rs.old Normal file
View File

@ -0,0 +1,484 @@
#![allow(dead_code)]
use std::{
f32::consts::PI,
fs::File,
i16,
io::{Read, Write},
ops::{Add, Div, Mul, Sub},
};
mod bfsk;
mod complex;
pub mod fft;
mod filtering;
mod iq;
mod nco;
mod signal;
mod units;
mod windows;
use bfsk::BFSKMod;
use complex::Complex;
use complex::Complex32;
use fft::DFTAlgorithm;
use nco::Nco;
use eframe::egui::{self, Color32, Context, Vec2, Vec2b, debug_text::print};
use egui_plot::{self, Bar, BarChart, Legend, Line, LineStyle, Plot, PlotPoints, VLine};
use plotters::style::Color;
use crate::{
bfsk::BFSKDem,
fft::{FFT, dft::NaiveDFT},
filtering::{
fir::FIRFilter,
impulse_response::design::{self, frequency_response, ir_from_transfer_function},
},
iq::IQSampler,
units::frequency::{self, hz_to_rad_per_sample},
};
// Utilities
fn map<T>(input: T, in_min: T, in_max: T, out_min: T, out_max: T) -> T
where
T: Clone + Add<Output = T> + Mul<Output = T> + Sub<Output = T> + Div<Output = T>,
{
((input - in_min.clone()) / (in_max - in_min)) * (out_max - out_min.clone()) + out_min
}
fn main() {
modulate();
println!("Demodulating");
//demodulate();
//return;
let native_options = eframe::NativeOptions::default();
let _ = eframe::run_native(
"Egui",
native_options,
Box::new(|cc| Ok(Box::new(EguiApp::new(cc)))),
);
}
#[derive(Default)]
struct EguiApp {
samples: Vec<f32>,
iq: Vec<Complex32>,
dem: Vec<f32>,
vlines: Vec<f32>,
elg_sampling: Vec<f32>,
elg_late: Vec<f32>,
elg_eary: Vec<f32>,
elg_error: Vec<f32>,
eye_diagram: Vec<Vec<f32>>,
}
const BAUD_RATE: u32 = 1200;
impl EguiApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap();
let samples = reader.samples::<i16>();
let sample_count = 10_000;
let input_test = samples
.take(sample_count)
.map(|x| x.unwrap() as f32 / i16::MAX as f32)
.collect::<Vec<_>>();
// Modulation parameters
let frequency = 1700.;
let deviation = 500.;
// Data parameters
let sample_rate = 48000;
let baud_rate = BAUD_RATE;
let sample_per_symbol = sample_rate / baud_rate;
let vlines = (0..sample_count)
.filter(|i| i % sample_per_symbol as usize == 0)
.map(|x| x as f32 / sample_count as f32)
.collect();
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
let iq = input_test
.iter()
.map(|x| iq_sampler.sample(*x))
.collect::<Vec<_>>();
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
let corellator_pos = (0..(sample_per_symbol * 1))
.map(|_| {
nco_pos.step();
nco_pos.cexp()
})
.collect::<Vec<_>>();
let corellator_neg = (0..(sample_per_symbol * 1))
.map(|_| {
nco_neg.step();
nco_neg.cexp()
})
.collect::<Vec<_>>();
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
let mut dem = vec![];
let mut loop_filter = FIRFilter::new(&[Complex32::new(1., 0.); 1]);
for x in &iq {
let pos = matched_filter_pos.next(x.clone());
let neg = matched_filter_neg.next(*x);
dem.push(
loop_filter
.next(Complex32::new(pos.mag() - neg.mag(), 0.))
.re,
);
}
// Symbol recovery
let mut sample_ids = vec![];
let mut early_ids = vec![];
let mut late_ids = vec![];
let mut error_vals = vec![];
let delta = 0.4;
let alpha = 0.1;
let mut filter = FIRFilter::new(&vec![Complex32::new(1., 0.); 10]);
let mut current_sps = sample_per_symbol as f32;
let mut current_position = current_sps / 2. + 23.;
let mut eye_diagram = vec![];
while current_position < dem.len() as f32 {
// Sample before after
let early_id = (current_position - (delta * current_sps)).max(0.).floor() as u32;
let late_id = (current_position + (delta * current_sps)).max(0.).floor() as u32;
if late_id as usize >= dem.len() {
break;
}
let early = dem[early_id as usize];
let late = dem[late_id as usize];
let error = early - late;
let filtered_error = filter.next(Complex32::new(error, 0.0)).re;
//current_sps -= alpha * error;
//error_vals.push(filtered_error);
error_vals.push(error);
sample_ids.push(current_position);
early_ids.push(current_position - delta * current_sps);
late_ids.push(current_position + delta * current_sps);
/*
*/
let eye = ((current_position - current_sps / 2.).max(0.) as usize
..(current_position + current_sps / 2.).min(sample_count as f32) as usize)
.map(|i| dem[i])
.collect();
eye_diagram.push(eye);
//current_position += current_sps;
current_position +=
current_sps - current_sps * alpha * error - current_sps * 0.05 * filtered_error;
}
let elg_sampling = sample_ids
.iter()
.map(|x| *x / sample_count as f32)
.collect();
Self {
samples: input_test.clone(),
iq,
dem,
vlines,
elg_sampling,
eye_diagram,
elg_error: error_vals,
elg_late: late_ids.iter().map(|x| *x / sample_count as f32).collect(),
elg_eary: early_ids.iter().map(|x| *x / sample_count as f32).collect(),
}
}
}
impl eframe::App for EguiApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Hello World!");
Plot::new("Fourrier transform")
.show_grid(Vec2b::FALSE)
.legend(Legend::default())
.show(ui, |plot_ui| {
for (i, eye) in self.eye_diagram.iter().enumerate().skip(1) {
plot_ui.line(
Line::new(
"eye",
eye.iter()
.enumerate()
.map(|(i, x)| [i as f64 / eye.len() as f64, *x as f64])
.collect::<Vec<_>>(),
)
.width(1.)
.color(Color32::RED),
);
}
/*
self.vlines.iter().for_each(|l| {
plot_ui.vline(
VLine::new("Boundaries", *l as f64)
.color(Color32::LIGHT_BLUE)
.width(0.5)
.style(LineStyle::dashed_dense()),
);
});
*/
self.elg_sampling.iter().for_each(|l| {
plot_ui.vline(
VLine::new("ELG", *l as f64)
.color(Color32::WHITE)
.width(0.5)
.style(LineStyle::dotted_dense()),
);
});
/*
self.elg_late.iter().for_each(|l| {
plot_ui.vline(
VLine::new("LATE", *l as f64)
.color(Color32::BLUE)
.width(1.5)
.style(LineStyle::dotted_dense()),
);
});
self.elg_eary.iter().for_each(|l| {
plot_ui.vline(
VLine::new("EARLY", *l as f64)
.color(Color32::RED)
.width(1.5)
.style(LineStyle::dotted_dense()),
);
});
*/
/*
plot_ui.line(
Line::new(
"Passband",
self.samples
.iter()
.enumerate()
.map(|(i, x)| [i as f64 / self.samples.len() as f64, *x as f64])
.collect::<Vec<_>>(),
)
.width(2.),
);
*/
plot_ui.line(
Line::new(
"Demodulated",
self.dem
.iter()
.enumerate()
.map(|(i, x)| [i as f64 / self.iq.len() as f64, *x as f64])
.collect::<Vec<_>>(),
)
.width(2.),
);
plot_ui.line(
Line::new(
"Error",
self.elg_error
.iter()
.enumerate()
.map(|(i, x)| {
[i as f64 / self.elg_error.len() as f64, 100. * *x as f64]
})
.collect::<Vec<_>>(),
)
.width(2.),
);
})
});
}
}
fn demodulate() {
let mut reader = hound::WavReader::open("audio/modulated.wav").unwrap();
let samples = reader.samples::<i16>();
// Modulation parameters
let frequency = 1700.;
let deviation = 500.;
// Data parameters
let sample_rate = 48000;
let baud_rate = BAUD_RATE;
let sample_per_symbol = sample_rate / baud_rate;
let mut iq_sampler = IQSampler::new(hz_to_rad_per_sample(frequency, sample_rate as f32));
let iq = samples
.map(|x| iq_sampler.sample(x.unwrap() as f32 / i16::MAX as f32))
.collect::<Vec<_>>();
let mut nco_pos = Nco::new(hz_to_rad_per_sample(deviation, sample_rate as f32));
let mut nco_neg = Nco::new(hz_to_rad_per_sample(-deviation, sample_rate as f32));
let corellator_pos = (0..(sample_per_symbol * 1))
.map(|_| {
nco_pos.step();
nco_pos.cexp()
})
.collect::<Vec<_>>();
let corellator_neg = (0..(sample_per_symbol * 1))
.map(|_| {
nco_neg.step();
nco_neg.cexp()
})
.collect::<Vec<_>>();
let mut matched_filter_pos = FIRFilter::new(&corellator_pos);
let mut matched_filter_neg = FIRFilter::new(&corellator_neg);
let mut dem = vec![];
for x in &iq {
let pos = matched_filter_pos.next(x.clone());
let neg = matched_filter_neg.next(*x);
dem.push(pos.mag() - neg.mag());
}
// Symbol recovery
let mut bits = vec![];
let delta = 0.5;
let alpha = 0.01;
let mut current_sps = sample_per_symbol as f32;
let mut current_position = current_sps / 2.;
while current_position < dem.len() as f32 {
// Sample before after
let early_id = (current_position - (delta * current_sps)).max(0.).floor() as u32;
let late_id = (current_position + (delta * current_sps)).max(0.).floor() as u32;
if late_id as usize >= dem.len() {
break;
}
let early = dem[early_id as usize];
let late = dem[late_id as usize];
let error = early * early - late * late;
current_sps -= alpha * error;
bits.push(dem[current_position.floor() as usize] > 0.);
current_position += current_sps;
}
//assert!(bits.len() % 8 == 0);
let mut out_file = File::create("out.txt").unwrap();
let mut strip = 0;
let bit_slice = bits.as_slice();
for i in 0..100 {
let byte = bits_to_byte(&bit_slice[(i as usize)..(i as usize + 8)]);
if byte == 0b01010111u8 {
strip = i + 8;
}
}
for i in 0..strip {
bits.remove(i as usize);
}
for x in bits.chunks(8) {
if x.len() != 8 {
break;
}
out_file.write_all(&[bits_to_byte(x)]).unwrap();
}
}
fn modulate() {
// Modulation parameters
let frequency = 1700.;
let deviation = 500.;
// Data parameter
let sample_rate = 48000;
let baud_rate = BAUD_RATE;
// File to modulate
let f = File::open("s.txt").unwrap();
let mut bitstream = std::iter::repeat_n(0b01010101u8, 1)
.chain(std::iter::repeat_n(0b01010111u8, 1))
.chain(f.bytes().map(|x| x.unwrap()))
.chain(std::iter::repeat_n(0u8, 1))
.flat_map(byte_to_bits);
let mut modulator = BFSKMod::new(
sample_rate / baud_rate,
units::frequency::hz_to_rad_per_sample(deviation, sample_rate as f32),
&mut bitstream,
);
let mut lo = Nco::new(units::frequency::hz_to_rad_per_sample(
frequency,
sample_rate as f32,
));
let spec = hound::WavSpec {
channels: 1,
sample_rate,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};
let mut writer = hound::WavWriter::create("audio/modulated.wav", spec).unwrap();
for (s, up) in modulator.zip(lo) {
let sample = (s * up).re; // Project to I coords
let amplitude = i16::MAX as f32;
writer.write_sample((sample * amplitude) as i16).unwrap();
}
writer.finalize().unwrap();
}
fn byte_to_bits(byte: u8) -> Vec<bool> {
vec![
byte & 1 == 1,
(byte >> 1) & 1 == 1,
(byte >> 2) & 1 == 1,
(byte >> 3) & 1 == 1,
(byte >> 4) & 1 == 1,
(byte >> 5) & 1 == 1,
(byte >> 6) & 1 == 1,
(byte >> 7) & 1 == 1,
]
}
/*
fn bits_to_byte(bits: &[bool]) -> u8 {
bits[7] as u8 |
bits[6] as u8 >> 1 |
bits[5] as u8 >> 2 |
bits[4] as u8 >> 3 |
bits[3] as u8 >> 4 |
bits[2] as u8 >> 5 |
bits[1] as u8 >> 6 |
bits[0] as u8 >> 7
}
*/
fn bits_to_byte(bits: &[bool]) -> u8 {
bits[0] as u8
| (bits[1] as u8) << 1
| (bits[2] as u8) << 2
| (bits[3] as u8) << 3
| (bits[4] as u8) << 4
| (bits[5] as u8) << 5
| (bits[6] as u8) << 6
| (bits[7] as u8) << 7
}

0
src/signal.rs Normal file
View File