From be7b90550706eea036839cba25c23d93a01ddf17 Mon Sep 17 00:00:00 2001 From: William P Date: Sun, 9 Nov 2025 22:43:24 -0500 Subject: [PATCH] port more from osmocom --- cellular/src/amps.rs | 16 +-- cellular/src/display.rs | 13 ++ cellular/src/emphasis.rs | 57 +++++++++ cellular/src/iir_filter.rs | 126 ++++++++++++++++--- cellular/src/jitter.rs | 41 ++++++ cellular/src/libmobile_sender.rs | 207 +++++++++++++++++++++++++++---- cellular/src/main.rs | 4 + cellular/src/samplerate.rs | 153 +++++++++++++++++++---- cellular/src/sdr.rs | 9 +- cellular/src/wave.rs | 13 ++ 10 files changed, 569 insertions(+), 70 deletions(-) create mode 100644 cellular/src/display.rs create mode 100644 cellular/src/emphasis.rs create mode 100644 cellular/src/jitter.rs create mode 100644 cellular/src/wave.rs diff --git a/cellular/src/amps.rs b/cellular/src/amps.rs index 312194f..8436fc3 100644 --- a/cellular/src/amps.rs +++ b/cellular/src/amps.rs @@ -1,17 +1,19 @@ +const FSK_MAX_BITS: i32 = 1032; + enum DSPMode { OFF, // Channel not active - AUDIO_RX_AUDIO_TX, // stream audio - AUDIO_RX_FRAME_TX, // stream audio and send frames - AUDIO_RX_SILENCE_TX, // stream audio and send silence - FRAME_RX_FRAME_TX, // send and receive frames + AudioRxAudioTx, // stream audio + AudioRxFrameTx, // stream audio and send frames + AudioRxSilenceTx, // stream audio and send silence + FrameRxFrameTx, // send and receive frames } enum AMPSChannelType { CC, // control channel PC, // paging channel - CC_PC, // combined control+paging + CcPc, // combined control+paging VC, // voice channel - CC_PC_VC, // combined control+paging+voice + CcPcVc, // combined control+paging+voice } enum AMPSState { @@ -20,7 +22,7 @@ enum AMPSState { BUSY, // channel busy (call in progress) } -enum FSK_RX_Sync { +enum FSKRxSync { NONE, // not in sync, waiting for valid dotting sequence DOTTING, // received a valid dotting sequence, check for sync sequence POSITIVE, // valid sync, read all bits from the frame diff --git a/cellular/src/display.rs b/cellular/src/display.rs new file mode 100644 index 0000000..23b1621 --- /dev/null +++ b/cellular/src/display.rs @@ -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 } +} \ No newline at end of file diff --git a/cellular/src/emphasis.rs b/cellular/src/emphasis.rs new file mode 100644 index 0000000..0b7480c --- /dev/null +++ b/cellular/src/emphasis.rs @@ -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); + } + } +} \ No newline at end of file diff --git a/cellular/src/iir_filter.rs b/cellular/src/iir_filter.rs index 246b1a1..00f65c9 100644 --- a/cellular/src/iir_filter.rs +++ b/cellular/src/iir_filter.rs @@ -1,36 +1,130 @@ -struct IIRFilter { - iter: i32, - a0: f64, - a1: f64, - a2: f64, - b1: f64, - b2: f64, - z1: f64, - z2: f64, +type Sample = f64; + +#[derive(Clone)] +pub struct IIRFilter { + pub iter: i32, + pub a0: f64, + pub a1: f64, + pub a2: f64, + pub b1: f64, + pub b2: f64, + pub z1: Vec, // Changed to Vec for iterations + pub z2: Vec, // Changed to Vec for iterations } impl IIRFilter { 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) { - + 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) { - + 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; + } } } \ No newline at end of file diff --git a/cellular/src/jitter.rs b/cellular/src/jitter.rs new file mode 100644 index 0000000..a50ff47 --- /dev/null +++ b/cellular/src/jitter.rs @@ -0,0 +1,41 @@ +type Sample = f64; + +#[derive(Clone)] +pub struct Jitter { + pub buffer: Vec, + 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 + } +} \ No newline at end of file diff --git a/cellular/src/libmobile_sender.rs b/cellular/src/libmobile_sender.rs index 2ba2b87..fa03af0 100644 --- a/cellular/src/libmobile_sender.rs +++ b/cellular/src/libmobile_sender.rs @@ -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 { - NONE = 0, - TONE, - NOTONE, - POSITIVE, - NEGATIVE, + None = 0, + Tone, + NoTone, + Positive, + Negative, } +#[derive(Clone)] struct Sender { - // System Info - channel: char, - send_freq: f64, - recv_freq: f64, - + next: *mut Sender, + slave: *mut Sender, + master: *mut Sender, + + // System info + kanal: String, + sendefrequenz: f64, + empfangsfrequenz: f64, + ruffrequenz: f64, + // FM/AM levels am: i32, max_deviation: f64, @@ -21,25 +39,168 @@ struct Sender { max_display: f64, // Audio - audio: void, - device: char, - audio_open: void, - audio_start: i32, - audio_close: void, - audio_write: i32, - audio_get_tosend: i32, + audio: *mut c_void, + device: String, + audio_open: Option *mut c_void>, + audio_start: Option i32>, + audio_close: Option, + audio_write: Option i32>, + audio_read: Option i32>, + audio_get_tosend: Option i32>, samplerate: i32, - samplerate_state: SampleRate, + srstate: SampleRate, rx_gain: f64, tx_gain: f64, pre_emphasis: i32, de_emphasis: i32, - epmphasis_state: EmphasisState, + estate: Emphasis, - // Loopback + // Loopback test loopback: i32, - // Record & Playback - + // Record and playback + write_rx_wave: Option, + write_tx_wave: Option, + read_rx_wave: Option, + read_tx_wave: Option, + wave_rx_rec: WaveRec, + wave_tx_rec: WaveRec, + wave_rx_play: WavePlay, + wave_tx_play: WavePlay, -} \ No newline at end of file + // 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) {} \ No newline at end of file diff --git a/cellular/src/main.rs b/cellular/src/main.rs index aebd30a..62aaec6 100644 --- a/cellular/src/main.rs +++ b/cellular/src/main.rs @@ -6,6 +6,10 @@ mod amps; mod libmobile_sender; mod iir_filter; mod samplerate; +mod emphasis; +mod jitter; +mod wave; +mod display; fn main() -> Result<(), Box> { let stdout = stdout(); diff --git a/cellular/src/samplerate.rs b/cellular/src/samplerate.rs index 4d38fae..9acfc81 100644 --- a/cellular/src/samplerate.rs +++ b/cellular/src/samplerate.rs @@ -1,40 +1,149 @@ -struct SampleRate { - factor: f64, - filter_cutoff: f64, - down: DownSampler, - up: UpSampler, +use crate::iir_filter::IIRFilter; + +type Sample = f64; + +#[derive(Clone)] +pub struct SampleRate { + pub factor: f64, + pub filter_cutoff: f64, + pub down: DownSampler, + pub up: UpSampler, } -struct DownSampler { - lp: IIRFilter, - last_sample: Sample, - in_index: f64, +#[derive(Clone)] +pub struct DownSampler { + pub lp: IIRFilter, + pub last_sample: Sample, + pub in_index: f64, } -struct UpSampler { - lp: IIRFilter, - current_sample: Sample, - last_sample: Sample, - in_index: f64, +#[derive(Clone)] +pub struct UpSampler { + pub lp: IIRFilter, + pub current_sample: Sample, + pub last_sample: Sample, + pub in_index: f64, } impl SampleRate { 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(()) - // or Err(error_code) if there's an error - } + self.factor = high_samplerate / low_samplerate; + 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(()) } - pub fn upsample_input_num(&mut self, state: SampleRate, output_num: i32) -> Result<(), i32> { - Ok(()) + pub fn downsample_input_num(&mut self, output_num: i32) -> i32 { + 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(()) } diff --git a/cellular/src/sdr.rs b/cellular/src/sdr.rs index 3ad4602..731c3a4 100644 --- a/cellular/src/sdr.rs +++ b/cellular/src/sdr.rs @@ -54,7 +54,12 @@ impl SdrTransmitter { // Helper function to create Args for a specific device pub fn device_args(driver: &str, serial: Option<&str>) -> Args { match serial { - Some(serial) => Args::from(&format!("driver={},serial={}", driver, serial)), - None => Args::from(&format!("driver={}", driver)), + Some(serial) => Args::from(format!("driver={},serial={}", driver, serial).as_str()), + None => Args::from(format!("driver={}", driver).as_str()), } +} + +pub fn find_device() -> Result> { + // Stub: return default device args + Ok(device_args("hackrf", None)) } \ No newline at end of file diff --git a/cellular/src/wave.rs b/cellular/src/wave.rs new file mode 100644 index 0000000..e586636 --- /dev/null +++ b/cellular/src/wave.rs @@ -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 } +} \ No newline at end of file