use num_complex::Complex32;
use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use crate::demodulate::dvb_t_frame_demodulate;
use crate::fec::PunctureRate;
use crate::modulate::{ConstellationOrder, dvb_t_frame_modulate};
use crate::waveform::dvb_t::{DvbTFrameParams, GuardInterval, NbBandwidth};
use crate::waveform::dvb_t_tps::TpsWord;
fn parse_guard(s: &str) -> PyResult<GuardInterval> {
match s {
"1/32" => Ok(GuardInterval::G1_32),
"1/16" => Ok(GuardInterval::G1_16),
"1/8" => Ok(GuardInterval::G1_8),
"1/4" => Ok(GuardInterval::G1_4),
other => Err(PyValueError::new_err(format!(
"unknown guard interval {other:?} (expected 1/32, 1/16, 1/8, 1/4)"
))),
}
}
fn guard_str(g: GuardInterval) -> &'static str {
match g {
GuardInterval::G1_32 => "1/32",
GuardInterval::G1_16 => "1/16",
GuardInterval::G1_8 => "1/8",
GuardInterval::G1_4 => "1/4",
}
}
fn parse_dvb_t_constellation(s: &str) -> PyResult<ConstellationOrder> {
match s {
"qpsk" => Ok(ConstellationOrder::Qpsk),
"qam16" => Ok(ConstellationOrder::Qam16),
"qam64" => Ok(ConstellationOrder::Qam64),
other => Err(PyValueError::new_err(format!(
"unknown DVB-T constellation {other:?} (expected qpsk, qam16, qam64)"
))),
}
}
fn constellation_str(c: ConstellationOrder) -> PyResult<&'static str> {
match c {
ConstellationOrder::Qpsk => Ok("qpsk"),
ConstellationOrder::Qam16 => Ok("qam16"),
ConstellationOrder::Qam64 => Ok("qam64"),
other => Err(PyValueError::new_err(format!(
"{other:?} is not a DVB-T constellation"
))),
}
}
fn parse_rate(s: &str) -> PyResult<PunctureRate> {
match s {
"1/2" => Ok(PunctureRate::R1_2),
"2/3" => Ok(PunctureRate::R2_3),
"3/4" => Ok(PunctureRate::R3_4),
"5/6" => Ok(PunctureRate::R5_6),
"7/8" => Ok(PunctureRate::R7_8),
other => Err(PyValueError::new_err(format!(
"unknown code rate {other:?} (expected 1/2, 2/3, 3/4, 5/6, 7/8)"
))),
}
}
fn rate_str(r: PunctureRate) -> &'static str {
match r {
PunctureRate::R1_2 => "1/2",
PunctureRate::R2_3 => "2/3",
PunctureRate::R3_4 => "3/4",
PunctureRate::R5_6 => "5/6",
PunctureRate::R7_8 => "7/8",
}
}
#[pyclass(name = "DvbTFrameParams", skip_from_py_object)]
#[derive(Clone)]
pub struct PyDvbTFrameParams {
inner: DvbTFrameParams,
}
#[pymethods]
impl PyDvbTFrameParams {
#[new]
#[pyo3(signature = (guard, constellation, code_rate, frame_number = 0, cell_id = 0))]
fn new(
guard: &str,
constellation: &str,
code_rate: &str,
frame_number: u8,
cell_id: u8,
) -> PyResult<Self> {
Ok(Self {
inner: DvbTFrameParams {
guard: parse_guard(guard)?,
constellation: parse_dvb_t_constellation(constellation)?,
code_rate: parse_rate(code_rate)?,
frame_number,
cell_id,
},
})
}
#[getter]
fn guard(&self) -> &'static str {
guard_str(self.inner.guard)
}
#[getter]
fn constellation(&self) -> PyResult<&'static str> {
constellation_str(self.inner.constellation)
}
#[getter]
fn code_rate(&self) -> &'static str {
rate_str(self.inner.code_rate)
}
#[getter]
fn frame_number(&self) -> u8 {
self.inner.frame_number
}
#[getter]
fn cell_id(&self) -> u8 {
self.inner.cell_id
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"DvbTFrameParams(guard={:?}, constellation={:?}, code_rate={:?}, frame_number={}, cell_id={})",
guard_str(self.inner.guard),
constellation_str(self.inner.constellation)?,
rate_str(self.inner.code_rate),
self.inner.frame_number,
self.inner.cell_id,
))
}
}
#[pyclass(name = "TpsWord", skip_from_py_object)]
#[derive(Clone)]
pub struct PyTpsWord {
inner: TpsWord,
}
#[pymethods]
impl PyTpsWord {
#[getter]
fn frame_number(&self) -> u8 {
self.inner.frame_number
}
#[getter]
fn constellation(&self) -> PyResult<&'static str> {
constellation_str(self.inner.constellation)
}
#[getter]
fn code_rate(&self) -> &'static str {
rate_str(self.inner.code_rate_hp)
}
#[getter]
fn guard(&self) -> &'static str {
guard_str(self.inner.guard)
}
#[getter]
fn cell_id(&self) -> u8 {
self.inner.cell_id
}
fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"TpsWord(frame_number={}, constellation={:?}, code_rate={:?}, guard={:?}, cell_id={})",
self.inner.frame_number,
constellation_str(self.inner.constellation)?,
rate_str(self.inner.code_rate_hp),
guard_str(self.inner.guard),
self.inner.cell_id,
))
}
}
#[pyclass(name = "DvbTFrame")]
pub struct PyDvbTFrame {
iq: Vec<Complex32>,
#[pyo3(get)]
n_symbols: usize,
#[pyo3(get)]
samples_per_symbol: usize,
}
#[pymethods]
impl PyDvbTFrame {
#[getter]
fn iq<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1<Complex32>> {
self.iq.clone().into_pyarray(py)
}
}
#[pyclass(name = "DvbTRxFrame")]
pub struct PyDvbTRxFrame {
payload: Vec<u8>,
#[pyo3(get)]
tps: PyTpsWord,
}
#[pymethods]
impl PyDvbTRxFrame {
#[getter]
fn payload<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
PyBytes::new(py, &self.payload)
}
}
#[pyfunction]
#[pyo3(name = "dvb_t_frame_modulate")]
fn dvb_t_frame_modulate_py(
params: &PyDvbTFrameParams,
payload: PyReadonlyArray1<'_, u8>,
) -> PyResult<PyDvbTFrame> {
let frame = dvb_t_frame_modulate(params.inner, payload.as_slice()?);
Ok(PyDvbTFrame {
iq: frame.iq,
n_symbols: frame.n_symbols,
samples_per_symbol: frame.samples_per_symbol,
})
}
#[pyfunction]
#[pyo3(name = "dvb_t_frame_demodulate")]
fn dvb_t_frame_demodulate_py(
params: &PyDvbTFrameParams,
iq: PyReadonlyArray1<'_, Complex32>,
n_symbols: usize,
payload_len: usize,
) -> PyResult<PyDvbTRxFrame> {
let rx = dvb_t_frame_demodulate(params.inner, iq.as_slice()?, n_symbols, payload_len)
.map_err(|e| PyValueError::new_err(e.to_string()))?;
Ok(PyDvbTRxFrame {
payload: rx.payload,
tps: PyTpsWord { inner: rx.tps },
})
}
#[pyfunction]
#[pyo3(name = "nb_bandwidth_fs")]
fn nb_bandwidth_fs(mode: &str) -> PyResult<f32> {
Ok(parse_nb_bandwidth(mode)?.fs())
}
#[pyfunction]
#[pyo3(name = "nb_bandwidth_occupied_hz")]
fn nb_bandwidth_occupied_hz(mode: &str) -> PyResult<f32> {
Ok(parse_nb_bandwidth(mode)?.occupied_hz())
}
fn parse_nb_bandwidth(s: &str) -> PyResult<NbBandwidth> {
match s {
"333khz" | "333k" => Ok(NbBandwidth::Bw333kHz),
"1mhz" | "1m" => Ok(NbBandwidth::Bw1MHz),
"2mhz" | "2m" => Ok(NbBandwidth::Bw2MHz),
other => Err(PyValueError::new_err(format!(
"unknown NB bandwidth {other:?} (expected 333khz, 1mhz, 2mhz)"
))),
}
}
pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyDvbTFrameParams>()?;
m.add_class::<PyDvbTFrame>()?;
m.add_class::<PyDvbTRxFrame>()?;
m.add_class::<PyTpsWord>()?;
m.add_function(wrap_pyfunction!(dvb_t_frame_modulate_py, m)?)?;
m.add_function(wrap_pyfunction!(dvb_t_frame_demodulate_py, m)?)?;
m.add_function(wrap_pyfunction!(nb_bandwidth_fs, m)?)?;
m.add_function(wrap_pyfunction!(nb_bandwidth_occupied_hz, m)?)?;
Ok(())
}