Working early late: 3Kb/s

This commit is contained in:
2025-09-29 12:08:39 +02:00
parent 1445887f2f
commit 6f01bbdc5d
4 changed files with 223 additions and 89 deletions

View File

@ -1,5 +1,6 @@
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.
!<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

1
s.txt
View File

@ -1,5 +1,6 @@
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

@ -4,23 +4,28 @@ use std::collections::VecDeque;
use crate::complex::Complex32;
pub struct FIRFilter
{
pub struct FIRFilter {
size: usize,
normalization: f32,
impulse_response: Box<[Complex32]>,
taps: VecDeque<Complex32>,
}
impl FIRFilter
{
impl FIRFilter {
pub fn new(impulse_response: &[Complex32]) -> Self {
let normalization = impulse_response.iter().copied().sum::<Complex32>().mag(); // DC normalization
println!("normalization factor {}", normalization);
FIRFilter {
size: impulse_response.len(),
impulse_response: impulse_response.iter().copied().collect(),
//normalization: impulse_response.iter().map(|x| x.mag()).reduce(|acc, e| acc.max(e)).unwrap(),
normalization: impulse_response.iter().copied().sum::<Complex32>().mag(), // DC normalization
/*
normalization: impulse_response
.iter()
.map(|x| x.mag())
.reduce(|acc, e| acc.max(e))
.unwrap(),
*/
normalization: normalization.max(0.001), // DC normalization
// TODO: Maybe we'd want other types of normalization (per frequency)
taps: VecDeque::from(vec![Complex32::zero(); impulse_response.len()]),
}
@ -29,11 +34,12 @@ impl FIRFilter
pub fn next(&mut self, next: Complex32) -> Complex32 {
let _ = self.taps.pop_front();
self.taps.push_back(next);
self.taps
.iter()
.zip(self.impulse_response.iter())
.map(|(tap, coef)| *tap * *coef)
.sum::<Complex32>() / self.normalization
self.taps.push_back(next);
self.taps
.iter()
.zip(self.impulse_response.iter())
.map(|(tap, coef)| *tap * *coef)
.sum::<Complex32>()
/ self.normalization
}
}

View File

@ -23,8 +23,8 @@ use complex::Complex32;
use fft::DFTAlgorithm;
use nco::Nco;
use eframe::egui::{self, Color32, debug_text::print};
use egui_plot::{self, Bar, BarChart, Legend, Line, LineStyle, Plot, PlotPoints};
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::{
@ -49,9 +49,15 @@ where
fn main() {
modulate();
println!("Demodulating");
//demodulate();
demodulate();
return;
let native_options = eframe::NativeOptions::default();
let _ = eframe::run_native("Egui", native_options, Box::new(|cc| Ok(Box::new(EguiApp::new(cc)))));
let _ = eframe::run_native(
"Egui",
native_options,
Box::new(|cc| Ok(Box::new(EguiApp::new(cc)))),
);
}
#[derive(Default)]
@ -60,15 +66,19 @@ struct EguiApp {
iq: Vec<Complex32>,
dem: Vec<f32>,
vlines: Vec<f32>,
elg_sampling: Vec<f32>,
eye_diagram: Vec<Vec<f32>>,
}
const BAUD_RATE: u32 = 1000;
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(10_000)
.take(sample_count)
.map(|x| x.unwrap() as f32 / i16::MAX as f32)
.collect::<Vec<_>>();
@ -79,6 +89,11 @@ impl EguiApp {
// 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
@ -86,29 +101,82 @@ impl EguiApp {
.map(|x| iq_sampler.sample(*x))
.collect::<Vec<_>>();
let mut dem = BFSKDem::new(
sample_rate / baud_rate,
hz_to_rad_per_sample(deviation, sample_rate as f32),
);
let mut demodulated = 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<_>>();
for x in iq.chunks((sample_rate / baud_rate) as usize) {
let samples = x
.iter()
.copied()
.chain(std::iter::repeat(Complex32::zero()))
.take((sample_rate / baud_rate) as usize)
.collect::<Vec<_>>();
demodulated.push(dem.demod(&samples));
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: demodulated
.iter()
.map(|b| if *b { 1. } else { 0. })
.collect(),
dem,
vlines,
elg_sampling,
eye_diagram,
}
}
}
@ -120,9 +188,41 @@ impl eframe::App for EguiApp {
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(
"Transfer function",
"Passband",
self.samples
.iter()
.enumerate()
@ -134,44 +234,11 @@ impl eframe::App for EguiApp {
plot_ui.line(
Line::new(
"RE",
self.iq
"Demodulated",
self.dem
.iter()
.enumerate()
.map(|(i, x)| [i as f64 / self.iq.len() as f64, x.re as f64])
.collect::<Vec<_>>(),
)
.width(2.),
);
plot_ui.line(
Line::new(
"IM",
self.iq
.iter()
.enumerate()
.map(|(i, x)| [i as f64 / self.iq.len() as f64, x.im as f64])
.collect::<Vec<_>>(),
)
.width(2.),
);
plot_ui.line(
Line::new(
"Bits",
self.iq
.iter()
.enumerate()
.map(|(i, x)| {
[
i as f64 / self.iq.len() as f64,
self.dem[(self.dem.len() as f32 * i as f32
/ self.iq.len() as f32)
.floor()
as usize]
as f64,
]
})
.map(|(i, x)| [i as f64 / self.iq.len() as f64, *x as f64])
.collect::<Vec<_>>(),
)
.width(2.),
@ -189,33 +256,88 @@ fn demodulate() {
let frequency = 1700.;
let deviation = 500.;
// Data parameter
// 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 = samples
let iq = samples
.map(|x| iq_sampler.sample(x.unwrap() as f32 / i16::MAX as f32))
.collect::<Vec<_>>();
let mut dem = BFSKDem::new(
sample_rate / baud_rate,
hz_to_rad_per_sample(deviation, sample_rate as f32),
);
let mut bits = 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<_>>();
for x in iq_samples.chunks((sample_rate / baud_rate) as usize) {
// Zero pad
//let zero_padded = x.iter().copied().chain(std::iter::repeat(Complex32::zero())).take(sample_rate as usize / baud_rate as usize).collect::<Vec<_>>();
//bits.push(dem.demod(&zero_padded));
bits.push(dem.demod(x));
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());
}
assert!(bits.len() % 8 == 0);
// 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) {
out_file.write_all(&[!bits_to_byte(x)]).unwrap();
if x.len() != 8 {
break;
}
out_file.write_all(&[bits_to_byte(x)]).unwrap();
}
}
@ -230,7 +352,11 @@ fn modulate() {
// File to modulate
let f = File::open("s.txt").unwrap();
let mut bitstream = f.bytes().flat_map(|b| byte_to_bits(b.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,