port more from osmocom

This commit is contained in:
2025-11-09 22:43:24 -05:00
parent 48dff5586c
commit be7b905507
10 changed files with 569 additions and 70 deletions

View File

@@ -1,17 +1,19 @@
const FSK_MAX_BITS: i32 = 1032;
enum DSPMode { enum DSPMode {
OFF, // Channel not active OFF, // Channel not active
AUDIO_RX_AUDIO_TX, // stream audio AudioRxAudioTx, // stream audio
AUDIO_RX_FRAME_TX, // stream audio and send frames AudioRxFrameTx, // stream audio and send frames
AUDIO_RX_SILENCE_TX, // stream audio and send silence AudioRxSilenceTx, // stream audio and send silence
FRAME_RX_FRAME_TX, // send and receive frames FrameRxFrameTx, // send and receive frames
} }
enum AMPSChannelType { enum AMPSChannelType {
CC, // control channel CC, // control channel
PC, // paging channel PC, // paging channel
CC_PC, // combined control+paging CcPc, // combined control+paging
VC, // voice channel VC, // voice channel
CC_PC_VC, // combined control+paging+voice CcPcVc, // combined control+paging+voice
} }
enum AMPSState { enum AMPSState {
@@ -20,7 +22,7 @@ enum AMPSState {
BUSY, // channel busy (call in progress) BUSY, // channel busy (call in progress)
} }
enum FSK_RX_Sync { enum FSKRxSync {
NONE, // not in sync, waiting for valid dotting sequence NONE, // not in sync, waiting for valid dotting sequence
DOTTING, // received a valid dotting sequence, check for sync sequence DOTTING, // received a valid dotting sequence, check for sync sequence
POSITIVE, // valid sync, read all bits from the frame POSITIVE, // valid sync, read all bits from the frame

13
cellular/src/display.rs Normal file
View File

@@ -0,0 +1,13 @@
// Stub for display
#[derive(Clone)]
pub struct DispWav;
#[derive(Clone)]
pub struct DispMeas;
impl DispWav {
pub fn new() -> Self { DispWav }
}
impl DispMeas {
pub fn new() -> Self { DispMeas }
}

57
cellular/src/emphasis.rs Normal file
View File

@@ -0,0 +1,57 @@
use crate::iir_filter::IIRFilter;
type Sample = f64;
#[derive(Clone)]
pub struct Emphasis {
pub pre_filter: IIRFilter,
pub de_filter: IIRFilter,
}
impl Emphasis {
pub fn new() -> Self {
Emphasis {
pre_filter: IIRFilter {
iter: 0,
a0: 0.0,
a1: 0.0,
a2: 0.0,
b1: 0.0,
b2: 0.0,
z1: Vec::new(),
z2: Vec::new(),
},
de_filter: IIRFilter {
iter: 0,
a0: 0.0,
a1: 0.0,
a2: 0.0,
b1: 0.0,
b2: 0.0,
z1: Vec::new(),
z2: Vec::new(),
},
}
}
pub fn init(&mut self, samplerate: i32, pre_emphasis: i32, de_emphasis: i32) {
if pre_emphasis != 0 {
self.pre_filter.highpass_init(300.0, samplerate, 1); // Boost highs
}
if de_emphasis != 0 {
self.de_filter.lowpass_init(3000.0, samplerate, 1); // Cut highs
}
}
pub fn process_pre(&mut self, samples: &mut [Sample]) {
if self.pre_filter.iter > 0 {
self.pre_filter.process(samples);
}
}
pub fn process_de(&mut self, samples: &mut [Sample]) {
if self.de_filter.iter > 0 {
self.de_filter.process(samples);
}
}
}

View File

@@ -1,36 +1,130 @@
struct IIRFilter { type Sample = f64;
iter: i32,
a0: f64, #[derive(Clone)]
a1: f64, pub struct IIRFilter {
a2: f64, pub iter: i32,
b1: f64, pub a0: f64,
b2: f64, pub a1: f64,
z1: f64, pub a2: f64,
z2: f64, pub b1: f64,
pub b2: f64,
pub z1: Vec<f64>, // Changed to Vec for iterations
pub z2: Vec<f64>, // Changed to Vec for iterations
} }
impl IIRFilter { impl IIRFilter {
pub fn lowpass_init(&mut self, frequency: f64, samplerate: i32, iterations: i32) { pub fn lowpass_init(&mut self, frequency: f64, samplerate: i32, iterations: i32) {
if iterations > 64 {
panic!("Too many iterations, please fix!");
}
let sr = samplerate as f64;
let fc = frequency / sr; // Normalized frequency
let q = (0.5f64).sqrt().powf(1.0 / iterations as f64); // Q factor for iterations
let k = (std::f64::consts::PI * fc).tan();
let norm = 1.0 / (1.0 + k / q + k * k);
self.a0 = k * k * norm;
self.a1 = 2.0 * self.a0;
self.a2 = self.a0;
self.b1 = 2.0 * (k * k - 1.0) * norm;
self.b2 = (1.0 - k / q + k * k) * norm;
self.iter = iterations;
self.z1 = vec![0.0; iterations as usize];
self.z2 = vec![0.0; iterations as usize];
} }
pub fn highpass_init(&mut self, frequency: f64, samplerate: i32, iterations: i32) { pub fn highpass_init(&mut self, frequency: f64, samplerate: i32, iterations: i32) {
if iterations > 64 {
panic!("Too many iterations, please fix!");
}
let sr = samplerate as f64;
let fc = frequency / sr;
let q = (0.5f64).sqrt().powf(1.0 / iterations as f64);
let k = (std::f64::consts::PI * fc).tan();
let norm = 1.0 / (1.0 + k / q + k * k);
self.a0 = 1.0 * norm;
self.a1 = -2.0 * self.a0;
self.a2 = self.a0;
self.b1 = 2.0 * (k * k - 1.0) * norm;
self.b2 = (1.0 - k / q + k * k) * norm;
self.iter = iterations;
self.z1 = vec![0.0; iterations as usize];
self.z2 = vec![0.0; iterations as usize];
} }
pub fn bandpass_init(&mut self, frequency: f64, samplerate: i32, iterations: i32) { pub fn bandpass_init(&mut self, frequency: f64, samplerate: i32, iterations: i32) {
if iterations > 64 {
panic!("Too many iterations, please fix!");
}
let sr = samplerate as f64;
let fc = frequency / sr;
let q = (0.5f64).sqrt().powf(1.0 / iterations as f64);
let k = (std::f64::consts::PI * fc).tan();
let norm = 1.0 / (1.0 + k / q + k * k);
self.a0 = k / q * norm;
self.a1 = 0.0;
self.a2 = -self.a0;
self.b1 = 2.0 * (k * k - 1.0) * norm;
self.b2 = (1.0 - k / q + k * k) * norm;
self.iter = iterations;
self.z1 = vec![0.0; iterations as usize];
self.z2 = vec![0.0; iterations as usize];
} }
pub fn notch_init(&mut self, frequency: f64, samplerate: i32, iterations: i32, Q: f64) { pub fn notch_init(&mut self, frequency: f64, samplerate: i32, iterations: i32, q: f64) {
if iterations > 64 {
panic!("Too many iterations, please fix!");
}
let sr = samplerate as f64;
let fc = frequency / sr;
let k = (std::f64::consts::PI * fc).tan();
let norm = 1.0 / (1.0 + k / q + k * k);
self.a0 = (1.0 + k * k) * norm;
self.a1 = 2.0 * (k * k - 1.0) * norm;
self.a2 = self.a0;
self.b1 = self.a1;
self.b2 = (1.0 - k / q + k * k) * norm;
self.iter = iterations;
self.z1 = vec![0.0; iterations as usize];
self.z2 = vec![0.0; iterations as usize];
} }
pub fn process(&mut self, samples: Sample, length: i32) { pub fn process(&mut self, samples: &mut [Sample]) {
let a0 = self.a0;
let a1 = self.a1;
let a2 = self.a2;
let b1 = self.b1;
let b2 = self.b2;
let iterations = self.iter as usize;
for sample in samples.iter_mut() {
let mut input = *sample + 0.000000001; // As in C
for j in 0..iterations {
let out = input * a0 + self.z1[j];
self.z1[j] = input * a1 + self.z2[j] - b1 * out;
self.z2[j] = input * a2 - b2 * out;
input = out;
}
*sample = input;
}
} }
pub fn process_baseband(&mut self, filter: IIRFilter, baseband: f64, length: i32) { pub fn process_baseband(&mut self, baseband: &mut [f32], length: i32) {
let a0 = self.a0;
let a1 = self.a1;
let a2 = self.a2;
let b1 = self.b1;
let b2 = self.b2;
let iterations = self.iter as usize;
for i in 0..length as usize {
let mut input = baseband[i * 2] as f64 + 0.000000001; // Process real part
for j in 0..iterations {
let out = input * a0 + self.z1[j];
self.z1[j] = input * a1 + self.z2[j] - b1 * out;
self.z2[j] = input * a2 - b2 * out;
input = out;
}
baseband[i * 2] = input as f32;
}
} }
} }

41
cellular/src/jitter.rs Normal file
View File

@@ -0,0 +1,41 @@
type Sample = f64;
#[derive(Clone)]
pub struct Jitter {
pub buffer: Vec<Sample>,
pub read_pos: usize,
pub write_pos: usize,
pub size: usize,
}
impl Jitter {
pub fn new(size: usize) -> Self {
Jitter {
buffer: vec![0.0; size],
read_pos: 0,
write_pos: 0,
size,
}
}
pub fn write(&mut self, samples: &[Sample]) {
for &sample in samples {
self.buffer[self.write_pos] = sample;
self.write_pos = (self.write_pos + 1) % self.size;
}
}
pub fn read(&mut self, output: &mut [Sample]) -> usize {
let mut count = 0;
for i in 0..output.len() {
if self.read_pos != self.write_pos {
output[i] = self.buffer[self.read_pos];
self.read_pos = (self.read_pos + 1) % self.size;
count += 1;
} else {
break;
}
}
count
}
}

View File

@@ -1,17 +1,35 @@
use std::ffi::{c_void, CStr, c_char};
use crate::emphasis::Emphasis;
use crate::jitter::Jitter;
use crate::wave::{WaveRec, WavePlay};
use crate::display::{DispWav, DispMeas};
use crate::samplerate::SampleRate;
// Type aliases
type Sample = f64;
#[repr(C)]
#[derive(Clone)]
enum PagingSignal { enum PagingSignal {
NONE = 0, None = 0,
TONE, Tone,
NOTONE, NoTone,
POSITIVE, Positive,
NEGATIVE, Negative,
} }
#[derive(Clone)]
struct Sender { struct Sender {
// System Info next: *mut Sender,
channel: char, slave: *mut Sender,
send_freq: f64, master: *mut Sender,
recv_freq: f64,
// System info
kanal: String,
sendefrequenz: f64,
empfangsfrequenz: f64,
ruffrequenz: f64,
// FM/AM levels // FM/AM levels
am: i32, am: i32,
max_deviation: f64, max_deviation: f64,
@@ -21,25 +39,168 @@ struct Sender {
max_display: f64, max_display: f64,
// Audio // Audio
audio: void, audio: *mut c_void,
device: char, device: String,
audio_open: void, audio_open: Option<fn(i32, *const std::ffi::c_char, *mut f64, *mut f64, *mut i32, i32, f64, i32, i32, f64, f64, f64, f64) -> *mut c_void>,
audio_start: i32, audio_start: Option<fn(*mut c_void) -> i32>,
audio_close: void, audio_close: Option<fn(*mut c_void)>,
audio_write: i32, audio_write: Option<fn(*mut c_void, *mut *mut Sample, *mut *mut u8, i32, *mut PagingSignal, *mut i32, i32) -> i32>,
audio_get_tosend: i32, audio_read: Option<fn(*mut c_void, *mut *mut Sample, i32, i32, *mut f64) -> i32>,
audio_get_tosend: Option<fn(*mut c_void, i32) -> i32>,
samplerate: i32, samplerate: i32,
samplerate_state: SampleRate, srstate: SampleRate,
rx_gain: f64, rx_gain: f64,
tx_gain: f64, tx_gain: f64,
pre_emphasis: i32, pre_emphasis: i32,
de_emphasis: i32, de_emphasis: i32,
epmphasis_state: EmphasisState, estate: Emphasis,
// Loopback // Loopback test
loopback: i32, loopback: i32,
// Record & Playback // Record and playback
write_rx_wave: Option<String>,
write_tx_wave: Option<String>,
read_rx_wave: Option<String>,
read_tx_wave: Option<String>,
wave_rx_rec: WaveRec,
wave_tx_rec: WaveRec,
wave_rx_play: WavePlay,
wave_tx_play: WavePlay,
} // Audio buffer for audio to send to transmitter
dejitter: Jitter,
loop_dejitter: Jitter,
loop_sequence: u16,
loop_timestamp: u32,
// Audio buffer for audio to send to caller (20ms = 160 samples @ 8000Hz)
rxbuf: [Sample; 160],
rxbuf_pos: i32,
// Paging tone
paging_signal: PagingSignal,
paging_on: i32,
// Display wave
dispwav: DispWav,
// Display measurements
dispmeas: DispMeas,
}
// Global variables
pub static mut SENDER_HEAD: *mut Sender = std::ptr::null_mut();
pub static mut CANT_RECOVER: i32 = 0;
pub static mut CHECK_CHANNEL: i32 = 0;
// Function stubs (to be implemented)
pub fn sender_create(
sender: *mut Sender,
kanal: *const c_char,
sendefrequenz: f64,
empfangsfrequenz: f64,
device: *const c_char,
use_sdr: i32,
samplerate: i32,
rx_gain: f64,
tx_gain: f64,
pre_emphasis: i32,
de_emphasis: i32,
write_rx_wave: *const c_char,
write_tx_wave: *const c_char,
read_rx_wave: *const c_char,
read_tx_wave: *const c_char,
loopback: i32,
paging_signal: PagingSignal,
) -> i32 {
unsafe {
if sender.is_null() {
return -1;
}
let s = &mut *sender;
s.kanal = CStr::from_ptr(kanal).to_string_lossy().into_owned();
s.sendefrequenz = sendefrequenz;
s.empfangsfrequenz = empfangsfrequenz;
s.device = CStr::from_ptr(device).to_string_lossy().into_owned();
s.samplerate = samplerate;
s.rx_gain = rx_gain;
s.tx_gain = tx_gain;
s.pre_emphasis = pre_emphasis;
s.de_emphasis = de_emphasis;
s.write_rx_wave = if !write_rx_wave.is_null() {
Some(CStr::from_ptr(write_rx_wave).to_string_lossy().into_owned())
} else {
None
};
s.write_tx_wave = if !write_tx_wave.is_null() {
Some(CStr::from_ptr(write_tx_wave).to_string_lossy().into_owned())
} else {
None
};
s.read_rx_wave = if !read_rx_wave.is_null() {
Some(CStr::from_ptr(read_rx_wave).to_string_lossy().into_owned())
} else {
None
};
s.read_tx_wave = if !read_tx_wave.is_null() {
Some(CStr::from_ptr(read_tx_wave).to_string_lossy().into_owned())
} else {
None
};
s.loopback = loopback;
s.paging_signal = paging_signal;
// Initialize dependencies
s.estate = Emphasis::new();
s.dejitter = Jitter::new(1600); // 10 * 160
s.loop_dejitter = Jitter::new(1600);
s.wave_rx_rec = WaveRec::new();
s.wave_tx_rec = WaveRec::new();
s.wave_rx_play = WavePlay::new();
s.wave_tx_play = WavePlay::new();
s.dispwav = DispWav::new();
s.dispmeas = DispMeas::new();
// TODO: Initialize srstate
// s.srstate = SampleRate::new(...);
// TODO: Initialize audio_open etc.
s.audio_open = None; // Stub
s.audio_start = None;
s.audio_close = None;
s.audio_write = None;
s.audio_read = None;
s.audio_get_tosend = None;
0
}
}
pub fn sender_destroy(sender: *mut Sender) {
if !sender.is_null() {
// TODO: Clean up resources
// For now, just deallocate if using Box or something, but since *mut, assume caller handles
}
}
// Stub implementations for other functions
pub fn sender_set_fm(_sender: *mut Sender, _max_deviation: f64, _max_modulation: f64, _speech_deviation: f64, _max_display: f64) {}
pub fn sender_set_am(_sender: *mut Sender, _max_modulation: f64, _speech_deviation: f64, _max_display: f64, _modulation_index: f64) {}
pub fn sender_open_audio(_buffer_size: i32, _interval: f64) -> i32 { 0 }
pub fn sender_start_audio() -> i32 { 0 }
pub fn process_sender_audio(_sender: *mut Sender, _quit: *mut i32, _samples: *mut *mut Sample, _power: *mut *mut u8, _buffer_size: i32) {}
pub fn sender_send(_sender: *mut Sender, _samples: *mut Sample, _power: *mut u8, _count: i32) {}
pub fn sender_receive(_sender: *mut Sender, _samples: *mut Sample, _count: i32, _rf_level_db: f64) {}
pub fn sender_paging(_sender: *mut Sender, _on: i32) {}
pub fn get_sender_by_empfangsfrequenz(freq: f64) -> *mut Sender {
unsafe {
let mut current = SENDER_HEAD;
while !current.is_null() {
if (*current).empfangsfrequenz == freq {
return current;
}
current = (*current).next;
}
std::ptr::null_mut()
}
}
pub fn sender_conceal(_spl: *mut u8, _len: i32, _priv: *mut c_void) {}

View File

@@ -6,6 +6,10 @@ mod amps;
mod libmobile_sender; mod libmobile_sender;
mod iir_filter; mod iir_filter;
mod samplerate; mod samplerate;
mod emphasis;
mod jitter;
mod wave;
mod display;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let stdout = stdout(); let stdout = stdout();

View File

@@ -1,40 +1,149 @@
struct SampleRate { use crate::iir_filter::IIRFilter;
factor: f64,
filter_cutoff: f64, type Sample = f64;
down: DownSampler,
up: UpSampler, #[derive(Clone)]
pub struct SampleRate {
pub factor: f64,
pub filter_cutoff: f64,
pub down: DownSampler,
pub up: UpSampler,
} }
struct DownSampler { #[derive(Clone)]
lp: IIRFilter, pub struct DownSampler {
last_sample: Sample, pub lp: IIRFilter,
in_index: f64, pub last_sample: Sample,
pub in_index: f64,
} }
struct UpSampler { #[derive(Clone)]
lp: IIRFilter, pub struct UpSampler {
current_sample: Sample, pub lp: IIRFilter,
last_sample: Sample, pub current_sample: Sample,
in_index: f64, pub last_sample: Sample,
pub in_index: f64,
} }
impl SampleRate { impl SampleRate {
pub fn init(&mut self, low_samplerate: f64, high_samplerate: f64, filter_cutoff: f64) -> Result<(), i32> { pub fn init(&mut self, low_samplerate: f64, high_samplerate: f64, filter_cutoff: f64) -> Result<(), i32> {
// Implementation goes here // clear state
self.factor = 0.0;
self.filter_cutoff = 0.0;
Ok(()) self.factor = high_samplerate / low_samplerate;
// or Err(error_code) if there's an error if self.factor < 1.0 {
} eprintln!("Software error: Low sample rate must be lower than high sample rate, aborting!");
return Err(-1);
}
pub fn downsample(&mut self, state: SampleRate, samples: Sample, input_num: i32) -> Result<(), i32> { self.filter_cutoff = filter_cutoff;
if self.filter_cutoff > 0.0 {
self.up.lp.lowpass_init(filter_cutoff, high_samplerate as i32, 2);
self.down.lp.lowpass_init(filter_cutoff, high_samplerate as i32, 2);
}
Ok(()) Ok(())
} }
pub fn upsample_input_num(&mut self, state: SampleRate, output_num: i32) -> Result<(), i32> { pub fn downsample_input_num(&mut self, output_num: i32) -> i32 {
Ok(()) let factor = self.factor;
let mut in_index = self.down.in_index;
let mut idx = 0;
for _ in 0..output_num {
in_index += factor;
idx = in_index as i32;
}
idx
} }
pub fn upsample_output_num(&mut self, state: SampleRate, input_num: i32) -> Result<(), i32> { pub fn downsample_output_num(&mut self, input_num: i32) -> i32 {
let factor = self.factor;
let mut in_index = self.down.in_index;
let mut output_num = 0;
let mut idx;
loop {
idx = in_index as i32;
if idx >= input_num {
break;
}
output_num += 1;
in_index += factor;
}
output_num
}
pub fn downsample(&mut self, samples: &mut [Sample], input_num: i32) -> i32 {
let factor = self.factor;
let mut output_num = 0;
let mut in_index = self.down.in_index;
let mut last_sample = self.down.last_sample;
let mut idx;
let mut diff;
// filter down if cutoff is set
if self.filter_cutoff > 0.0 {
self.down.lp.process(&mut samples[..input_num as usize]);
}
// allocate output buffer w/ safety margin
let output_size = ((input_num as f64) / factor + 0.5) as usize + 10;
let mut output = vec![0.0; output_size];
for i in 0.. {
idx = in_index as i32;
if idx >= input_num {
break;
}
diff = in_index - idx as f64;
if idx > 0 {
output[i] = samples[(idx-1) as usize] * (1.0 - diff) + samples[idx as usize] * diff;
} else {
output[i] = last_sample * (1.0 - diff) + samples[idx as usize] * diff;
}
output_num += 1;
in_index += factor;
}
// store last sample
if input_num > 0 {
self.down.last_sample = samples[(input_num -1) as usize];
}
// adjust in_index
in_index -= input_num as f64;
if (in_index as i32) < 0 {
in_index = 0.0;
}
self.down.in_index = in_index;
// copy output back to samples
for i in 0..output_num {
samples[i as usize] = output[i as usize];
}
output_num
}
pub fn upsample_input_num(&mut self, output_num: i32) -> i32 {
let factor = 1.0 / self.factor;
let mut in_index = self.up.in_index;
let mut idx = 0;
for _ in 0..output_num {
in_index += factor;
if in_index >= 1.0 {
idx += 1;
in_index -= 1.0;
}
}
idx
}
pub fn upsample_output_num(&mut self, input_num: i32) -> i32 {
Ok(()) Ok(())
} }

View File

@@ -54,7 +54,12 @@ impl SdrTransmitter {
// Helper function to create Args for a specific device // Helper function to create Args for a specific device
pub fn device_args(driver: &str, serial: Option<&str>) -> Args { pub fn device_args(driver: &str, serial: Option<&str>) -> Args {
match serial { match serial {
Some(serial) => Args::from(&format!("driver={},serial={}", driver, serial)), Some(serial) => Args::from(format!("driver={},serial={}", driver, serial).as_str()),
None => Args::from(&format!("driver={}", driver)), None => Args::from(format!("driver={}", driver).as_str()),
} }
}
pub fn find_device() -> Result<Args, Box<dyn std::error::Error>> {
// Stub: return default device args
Ok(device_args("hackrf", None))
} }

13
cellular/src/wave.rs Normal file
View File

@@ -0,0 +1,13 @@
// Stub for wave recording/playback
#[derive(Clone)]
pub struct WaveRec;
#[derive(Clone)]
pub struct WavePlay;
impl WaveRec {
pub fn new() -> Self { WaveRec }
}
impl WavePlay {
pub fn new() -> Self { WavePlay }
}