Skip to content

Commit

Permalink
Add symbol_rate, bit_rate, and sample_rate` to modulation classes
Browse files Browse the repository at this point in the history
Fixes #366
  • Loading branch information
mhostetter committed Jun 29, 2024
1 parent e2f7955 commit abe3e43
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 14 deletions.
43 changes: 36 additions & 7 deletions src/sdr/_modulation/_cpm.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def __init__(
index: float = 0.5,
symbol_labels: Literal["bin", "gray"] | npt.ArrayLike = "bin",
phase_offset: float = 0.0,
symbol_rate: float = 1.0,
samples_per_symbol: int = 8,
# pulse_shape: npt.ArrayLike | Literal["rect", "rc", "srrc", "gaussian"] = "rect",
pulse_shape: npt.ArrayLike | Literal["rect"] = "rect",
Expand All @@ -67,6 +68,7 @@ def __init__(
the new symbol labels.
phase_offset: A phase offset $\phi$ in degrees.
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
pulse_shape: The pulse shape $h[n]$ of the instantaneous frequency of the signal. If a string is passed,
the pulse shape is normalized such that the maximum value is 1.
Expand Down Expand Up @@ -111,6 +113,12 @@ def __init__(
raise TypeError(f"Argument 'phase_offset' must be a number, not {type(phase_offset)}.")
self._phase_offset = phase_offset # Phase offset in degrees

if not isinstance(symbol_rate, (int, float)):
raise TypeError(f"Argument 'symbol_rate' must be a number, not {type(symbol_rate)}.")
if not symbol_rate > 0:
raise ValueError(f"Argument 'symbol_rate' must be positive, not {symbol_rate}.")
self._symbol_rate = symbol_rate # symbols/s

if not isinstance(samples_per_symbol, int):
raise TypeError(f"Argument 'samples_per_symbol' must be an integer, not {type(samples_per_symbol)}.")
if not samples_per_symbol > 1:
Expand Down Expand Up @@ -261,13 +269,41 @@ def order(self) -> int:
"""
return self._order

@property
def symbol_rate(self) -> float:
r"""
The symbol rate $f_{sym}$ in symbols/s.
"""
return self._symbol_rate

@property
def bits_per_symbol(self) -> int:
r"""
The number of coded bits per symbol $k = \log_2 M$.
"""
return self._bits_per_symbol

@property
def bit_rate(self) -> float:
r"""
The bit rate $f_{b}$ in bits/s.
"""
return self.symbol_rate * self.bits_per_symbol

@property
def samples_per_symbol(self) -> int:
r"""
The number of samples per symbol $f_s / f_{sym}$.
"""
return self._samples_per_symbol

@property
def sample_rate(self) -> float:
r"""
The sample rate $f_s$ in samples/s.
"""
return self.symbol_rate * self.samples_per_symbol

@property
def index(self) -> float:
r"""
Expand All @@ -285,13 +321,6 @@ def phase_offset(self) -> float:
"""
return self._phase_offset

@property
def samples_per_symbol(self) -> int:
r"""
The number of samples per symbol $f_s / f_{sym}$.
"""
return self._samples_per_symbol

@property
def pulse_shape(self) -> np.ndarray:
r"""
Expand Down
43 changes: 36 additions & 7 deletions src/sdr/_modulation/_linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def __init__(
self,
symbol_map: npt.ArrayLike,
phase_offset: float = 0.0,
symbol_rate: float = 1.0,
samples_per_symbol: int = 8,
pulse_shape: npt.ArrayLike | Literal["rect", "rc", "srrc"] = "rect",
span: int | None = None,
Expand All @@ -54,6 +55,7 @@ def __init__(
are decimal symbols $s[k]$ and whose values are complex symbols $a[k]$, where $M$ is the
modulation order.
phase_offset: A phase offset $\phi$ in degrees to apply to `symbol_map`.
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
pulse_shape: The pulse shape $h[n]$ of the modulated signal.
Expand Down Expand Up @@ -83,6 +85,12 @@ def __init__(
raise TypeError(f"Argument 'phase_offset' must be a number, not {type(phase_offset)}.")
self._phase_offset = phase_offset # Phase offset in degrees

if not isinstance(symbol_rate, (int, float)):
raise TypeError(f"Argument 'symbol_rate' must be a number, not {type(symbol_rate)}.")
if not symbol_rate > 0:
raise ValueError(f"Argument 'symbol_rate' must be positive, not {symbol_rate}.")
self._symbol_rate = symbol_rate # symbols/s

if not isinstance(samples_per_symbol, int):
raise TypeError(f"Argument 'samples_per_symbol' must be an integer, not {type(samples_per_symbol)}.")
if not samples_per_symbol > 1:
Expand Down Expand Up @@ -277,13 +285,41 @@ def order(self) -> int:
"""
return self._order

@property
def symbol_rate(self) -> float:
r"""
The symbol rate $f_{sym}$ in symbols/s.
"""
return self._symbol_rate

@property
def bits_per_symbol(self) -> int:
r"""
The number of coded bits per symbol $k = \log_2 M$.
"""
return self._bits_per_symbol

@property
def bit_rate(self) -> float:
r"""
The bit rate $f_{b}$ in bits/s.
"""
return self.symbol_rate * self.bits_per_symbol

@property
def samples_per_symbol(self) -> int:
r"""
The number of samples per symbol $f_s / f_{sym}$.
"""
return self._samples_per_symbol

@property
def sample_rate(self) -> float:
r"""
The sample rate $f_s$ in samples/s.
"""
return self.symbol_rate * self.samples_per_symbol

@property
def phase_offset(self) -> float:
r"""
Expand All @@ -300,13 +336,6 @@ def symbol_map(self) -> npt.NDArray[np.complex128]:
"""
return self._symbol_map

@property
def samples_per_symbol(self) -> int:
r"""
The number of samples per symbol $f_s / f_{sym}$.
"""
return self._samples_per_symbol

@property
def pulse_shape(self) -> npt.NDArray[np.float64]:
r"""
Expand Down
3 changes: 3 additions & 0 deletions src/sdr/_modulation/_msk.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def __init__(
self,
phase_offset: float = 45,
symbol_labels: Literal["bin", "gray"] | npt.ArrayLike = "gray",
symbol_rate: float = 1.0,
samples_per_symbol: int = 8,
):
r"""
Expand All @@ -152,6 +153,7 @@ def __init__(
the new symbol labels. The default symbol labels are $0$ to $4-1$ for phases starting at $1 + 0j$
and going counter-clockwise around the unit circle.
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
See Also:
Expand All @@ -162,6 +164,7 @@ def __init__(
super().__init__(
phase_offset=phase_offset,
symbol_labels=symbol_labels,
symbol_rate=symbol_rate,
samples_per_symbol=samples_per_symbol,
pulse_shape=pulse_shape,
)
Expand Down
9 changes: 9 additions & 0 deletions src/sdr/_modulation/_psk.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def __init__(
order: int,
phase_offset: float = 0.0,
symbol_labels: Literal["bin", "gray"] | npt.ArrayLike = "gray",
symbol_rate: float = 1.0,
samples_per_symbol: int = 8,
pulse_shape: npt.ArrayLike | Literal["rect", "rc", "srrc"] = "rect",
span: int | None = None,
Expand All @@ -156,6 +157,7 @@ def __init__(
the new symbol labels. The default symbol labels are $0$ to $M-1$ for phases starting at $1 + 0j$
and going counter-clockwise around the unit circle.
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
pulse_shape: The pulse shape $h[n]$ of the modulated signal.
Expand All @@ -178,6 +180,7 @@ def __init__(
super().__init__(
base_symbol_map,
phase_offset=phase_offset,
symbol_rate=symbol_rate,
samples_per_symbol=samples_per_symbol,
pulse_shape=pulse_shape,
span=span,
Expand Down Expand Up @@ -591,6 +594,7 @@ def __init__(
order: int,
phase_offset: float = 0.0,
symbol_labels: Literal["bin", "gray"] | npt.ArrayLike = "gray",
symbol_rate: float = 1.0,
samples_per_symbol: int = 8,
pulse_shape: npt.ArrayLike | Literal["rect", "rc", "srrc"] = "rect",
span: int | None = None,
Expand All @@ -610,6 +614,7 @@ def __init__(
the new symbol labels. The default symbol labels are $0$ to $M-1$ for phases starting at $1 + 0j$
and going counter-clockwise around the unit circle.
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
pulse_shape: The pulse shape $h[n]$ of the modulated signal.
Expand All @@ -630,6 +635,7 @@ def __init__(
order,
phase_offset=phase_offset,
symbol_labels=symbol_labels,
symbol_rate=symbol_rate,
samples_per_symbol=samples_per_symbol,
pulse_shape=pulse_shape,
)
Expand Down Expand Up @@ -783,6 +789,7 @@ def __init__(
self,
phase_offset: float = 45,
symbol_labels: Literal["bin", "gray"] | npt.ArrayLike = "gray",
symbol_rate: float = 1.0,
samples_per_symbol: int = 8,
pulse_shape: npt.ArrayLike | Literal["rect", "rc", "srrc"] = "rect",
span: int | None = None,
Expand All @@ -801,6 +808,7 @@ def __init__(
the new symbol labels. The default symbol labels are $0$ to $4-1$ for phases starting at $1 + 0j$
and going counter-clockwise around the unit circle.
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
pulse_shape: The pulse shape $h[n]$ of the modulated signal.
Expand All @@ -821,6 +829,7 @@ def __init__(
4,
phase_offset=phase_offset,
symbol_labels=symbol_labels,
symbol_rate=symbol_rate,
samples_per_symbol=samples_per_symbol,
pulse_shape=pulse_shape,
span=span,
Expand Down

0 comments on commit abe3e43

Please sign in to comment.