use num_complex::Complex32;
use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyDict, PyList};
use crate::codec::psk31::Psk31Stream as RsPsk31Stream;
use crate::codec::varicode::{
VaricodeDecoder as RsVaricodeDecoder, VaricodeEncoder as RsVaricodeEncoder,
};
use crate::core::Block;
use crate::demodulate::psk31::{
Bpsk31Decider as RsBpsk31Decider, Bpsk31Demod as RsBpsk31Demod,
Qpsk31Decider as RsQpsk31Decider, Qpsk31Demod as RsQpsk31Demod,
};
use crate::modulate::psk31::{
Bpsk31Mod as RsBpsk31Mod, PSK31_BAUD, PSK31_POSTAMBLE_BITS, PSK31_PREAMBLE_BITS,
Qpsk31Mod as RsQpsk31Mod,
};
#[pyclass(name = "VaricodeEncoder")]
pub struct PyVaricodeEncoder(RsVaricodeEncoder);
#[pymethods]
impl PyVaricodeEncoder {
#[new]
fn new() -> Self {
Self(RsVaricodeEncoder::new())
}
fn push_preamble(&mut self, n: usize) {
self.0.push_preamble(n);
}
fn push_byte(&mut self, b: u8) {
self.0.push_byte(b);
}
fn push_postamble(&mut self, n: usize) {
self.0.push_postamble(n);
}
fn drain_bits<'py>(&mut self, py: Python<'py>) -> Bound<'py, PyArray1<u8>> {
self.0.drain_bits().into_pyarray(py)
}
fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[pyclass(name = "VaricodeDecoder")]
pub struct PyVaricodeDecoder(RsVaricodeDecoder);
#[pymethods]
impl PyVaricodeDecoder {
#[new]
fn new() -> Self {
Self(RsVaricodeDecoder::new())
}
fn push_bits(&mut self, bits: PyReadonlyArray1<'_, u8>) -> PyResult<()> {
let b = bits.as_slice()?;
for &bit in b {
self.0.push_bit(bit);
}
Ok(())
}
fn pop_bytes<'py>(&mut self, py: Python<'py>) -> Bound<'py, PyBytes> {
let mut chars = Vec::new();
while let Some(c) = self.0.pop_char() {
chars.push(c);
}
PyBytes::new(py, &chars)
}
}
#[pyclass(name = "Bpsk31Mod")]
pub struct PyBpsk31Mod(RsBpsk31Mod);
#[pymethods]
impl PyBpsk31Mod {
#[new]
fn new(fs: f32, rf_hz: f32, gain: f32) -> Self {
Self(RsBpsk31Mod::new(fs, rf_hz, gain))
}
fn set_gain(&mut self, g: f32) {
self.0.set_gain(g);
}
fn reset(&mut self) {
self.0.reset();
}
#[pyo3(signature = (text, preamble_bits=PSK31_PREAMBLE_BITS, postamble_bits=PSK31_POSTAMBLE_BITS))]
fn modulate_text<'py>(
&mut self,
py: Python<'py>,
text: &[u8],
preamble_bits: usize,
postamble_bits: usize,
) -> Bound<'py, PyArray1<Complex32>> {
self.0
.modulate_text(text, preamble_bits, postamble_bits)
.into_pyarray(py)
}
fn modulate_bits<'py>(
&mut self,
py: Python<'py>,
bits: PyReadonlyArray1<'_, u8>,
) -> PyResult<Bound<'py, PyArray1<Complex32>>> {
let b = bits.as_slice()?;
Ok(self.0.modulate_bits(b).into_pyarray(py))
}
}
#[pyclass(name = "Bpsk31Demod")]
pub struct PyBpsk31Demod(RsBpsk31Demod);
#[pymethods]
impl PyBpsk31Demod {
#[new]
fn new(fs: f32, rf_hz: f32, gain: f32) -> Self {
Self(RsBpsk31Demod::new(fs, rf_hz, gain))
}
fn set_gain(&mut self, g: f32) {
self.0.set_gain(g);
}
fn reset(&mut self) {
self.0.reset();
}
fn process<'py>(
&mut self,
py: Python<'py>,
iq: PyReadonlyArray1<'_, Complex32>,
) -> PyResult<Bound<'py, PyArray1<f32>>> {
let input = iq.as_slice()?;
let max_out = input.len(); let mut out = vec![0.0f32; max_out];
let wr = self.0.process(input, &mut out);
out.truncate(wr.out_written);
Ok(out.into_pyarray(py))
}
}
#[pyclass(name = "Qpsk31Mod")]
pub struct PyQpsk31Mod(RsQpsk31Mod);
#[pymethods]
impl PyQpsk31Mod {
#[new]
fn new(fs: f32, rf_hz: f32, gain: f32) -> Self {
Self(RsQpsk31Mod::new(fs, rf_hz, gain))
}
fn set_gain(&mut self, g: f32) {
self.0.set_gain(g);
}
fn reset(&mut self) {
self.0.reset();
}
#[pyo3(signature = (text, preamble_bits=PSK31_PREAMBLE_BITS, postamble_bits=PSK31_POSTAMBLE_BITS))]
fn modulate_text<'py>(
&mut self,
py: Python<'py>,
text: &[u8],
preamble_bits: usize,
postamble_bits: usize,
) -> Bound<'py, PyArray1<Complex32>> {
self.0
.modulate_text(text, preamble_bits, postamble_bits)
.into_pyarray(py)
}
fn modulate_bits<'py>(
&mut self,
py: Python<'py>,
bits: PyReadonlyArray1<'_, u8>,
) -> PyResult<Bound<'py, PyArray1<Complex32>>> {
let b = bits.as_slice()?;
Ok(self.0.modulate_bits(b).into_pyarray(py))
}
}
#[pyclass(name = "Qpsk31Demod")]
pub struct PyQpsk31Demod {
demod: RsQpsk31Demod,
decider: RsQpsk31Decider,
}
#[pymethods]
impl PyQpsk31Demod {
#[new]
fn new(fs: f32, rf_hz: f32, gain: f32) -> Self {
Self {
demod: RsQpsk31Demod::new(fs, rf_hz, gain),
decider: RsQpsk31Decider::new(),
}
}
fn set_gain(&mut self, g: f32) {
self.demod.set_gain(g);
}
fn reset(&mut self) {
self.demod.reset();
self.decider = RsQpsk31Decider::new();
}
fn process<'py>(
&mut self,
py: Python<'py>,
iq: PyReadonlyArray1<'_, Complex32>,
) -> PyResult<Bound<'py, PyArray1<f32>>> {
let input = iq.as_slice()?;
let max_out = input.len() * 2;
let mut soft = vec![0.0f32; max_out];
let wr = self.demod.process(input, &mut soft);
soft.truncate(wr.out_written);
self.decider.process(&soft, &mut []);
Ok(soft.into_pyarray(py))
}
fn flush<'py>(&mut self, py: Python<'py>) -> Bound<'py, PyArray1<u8>> {
let mut bits = Vec::new();
self.decider.flush(&mut bits);
bits.into_pyarray(py)
}
}
#[pyclass(name = "Bpsk31Decider")]
pub struct PyBpsk31Decider(RsBpsk31Decider);
#[pymethods]
impl PyBpsk31Decider {
#[new]
fn new() -> Self {
Self(RsBpsk31Decider::new())
}
fn process<'py>(
&mut self,
py: Python<'py>,
soft: PyReadonlyArray1<'_, f32>,
) -> PyResult<Bound<'py, PyArray1<u8>>> {
let input = soft.as_slice()?;
let mut out = vec![0u8; input.len()];
let wr = self.0.process(input, &mut out);
out.truncate(wr.out_written);
Ok(out.into_pyarray(py))
}
}
#[pyclass(name = "Psk31Stream")]
pub struct PyPsk31Stream(RsPsk31Stream);
#[pymethods]
impl PyPsk31Stream {
#[new]
#[pyo3(signature = (mode, fs, carrier_hz, gain=1.0))]
fn new(mode: &str, fs: f32, carrier_hz: f32, gain: f32) -> PyResult<Self> {
match mode.to_lowercase().as_str() {
"bpsk" | "bpsk31" => Ok(Self(RsPsk31Stream::new_bpsk(fs, carrier_hz, gain))),
"qpsk" | "qpsk31" => Ok(Self(RsPsk31Stream::new_qpsk(fs, carrier_hz, gain))),
_ => Err(pyo3::exceptions::PyValueError::new_err(format!(
"mode must be 'bpsk' or 'qpsk', got '{}'",
mode
))),
}
}
fn feed(&mut self, iq: PyReadonlyArray1<'_, Complex32>) -> PyResult<String> {
let input = iq.as_slice()?;
Ok(self.0.feed(input))
}
fn flush(&mut self) -> String {
self.0.flush()
}
}
#[pyfunction]
#[pyo3(signature = (candidates, carrier_hz, baud=PSK31_BAUD))]
pub fn best_psk31_sync<'py>(
_py: Python<'py>,
candidates: &Bound<'py, PyList>,
carrier_hz: f32,
baud: f32,
) -> PyResult<Option<Bound<'py, PyDict>>> {
let mut results = Vec::new();
for item in candidates.iter() {
let d = item.cast::<PyDict>()?;
let hz: f32 = d.get_item("carrier_hz")?.unwrap().extract()?;
let ts: usize = d.get_item("time_sym")?.unwrap().extract()?;
let fb: usize = d.get_item("freq_bin")?.unwrap().extract()?;
let sc: f32 = d.get_item("score")?.unwrap().extract()?;
results.push(crate::sync::psk31_sync::Psk31SyncResult {
carrier_hz: hz,
time_sym: ts,
freq_bin: fb,
score: sc,
soft_bits: vec![], });
}
match crate::util::best_sync(&results, carrier_hz, baud) {
Some((hz, time_sym)) => {
for item in candidates.iter() {
let d = item.cast::<PyDict>()?;
let ts: usize = d.get_item("time_sym")?.unwrap().extract()?;
let ch: f32 = d.get_item("carrier_hz")?.unwrap().extract()?;
if ts == time_sym && (ch - hz).abs() < 0.01 {
return Ok(Some(d.clone()));
}
}
Ok(None)
}
None => Ok(None),
}
}
#[pyfunction]
#[pyo3(signature = (iq, fs, base_hz, max_hz, min_carrier_syms=8, peak_margin_db=6.0, n_bits=1024, max_cand=10))]
#[allow(clippy::too_many_arguments)] pub fn psk31_sync<'py>(
py: Python<'py>,
iq: PyReadonlyArray1<'py, Complex32>,
fs: f32,
base_hz: f32,
max_hz: f32,
min_carrier_syms: usize,
peak_margin_db: f32,
n_bits: usize,
max_cand: usize,
) -> PyResult<Bound<'py, PyList>> {
let input = iq.as_slice()?;
let results = crate::sync::psk31_sync::psk31_sync(
input,
fs,
base_hz,
max_hz,
min_carrier_syms,
peak_margin_db,
n_bits,
max_cand,
);
let list = PyList::empty(py);
for r in results {
let d = PyDict::new(py);
d.set_item("time_sym", r.time_sym)?;
d.set_item("freq_bin", r.freq_bin)?;
d.set_item("carrier_hz", r.carrier_hz)?;
d.set_item("score", r.score)?;
let sb = r.soft_bits.into_pyarray(py);
d.set_item("soft_bits", sb)?;
list.append(d)?;
}
Ok(list)
}