Skip to content

Commit abe3e43

Browse files
committed
Add symbol_rate, bit_rate, and sample_rate` to modulation classes
Fixes #366
1 parent e2f7955 commit abe3e43

File tree

4 files changed

+84
-14
lines changed

4 files changed

+84
-14
lines changed

src/sdr/_modulation/_cpm.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def __init__(
4545
index: float = 0.5,
4646
symbol_labels: Literal["bin", "gray"] | npt.ArrayLike = "bin",
4747
phase_offset: float = 0.0,
48+
symbol_rate: float = 1.0,
4849
samples_per_symbol: int = 8,
4950
# pulse_shape: npt.ArrayLike | Literal["rect", "rc", "srrc", "gaussian"] = "rect",
5051
pulse_shape: npt.ArrayLike | Literal["rect"] = "rect",
@@ -67,6 +68,7 @@ def __init__(
6768
the new symbol labels.
6869
6970
phase_offset: A phase offset $\phi$ in degrees.
71+
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
7072
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
7173
pulse_shape: The pulse shape $h[n]$ of the instantaneous frequency of the signal. If a string is passed,
7274
the pulse shape is normalized such that the maximum value is 1.
@@ -111,6 +113,12 @@ def __init__(
111113
raise TypeError(f"Argument 'phase_offset' must be a number, not {type(phase_offset)}.")
112114
self._phase_offset = phase_offset # Phase offset in degrees
113115

116+
if not isinstance(symbol_rate, (int, float)):
117+
raise TypeError(f"Argument 'symbol_rate' must be a number, not {type(symbol_rate)}.")
118+
if not symbol_rate > 0:
119+
raise ValueError(f"Argument 'symbol_rate' must be positive, not {symbol_rate}.")
120+
self._symbol_rate = symbol_rate # symbols/s
121+
114122
if not isinstance(samples_per_symbol, int):
115123
raise TypeError(f"Argument 'samples_per_symbol' must be an integer, not {type(samples_per_symbol)}.")
116124
if not samples_per_symbol > 1:
@@ -261,13 +269,41 @@ def order(self) -> int:
261269
"""
262270
return self._order
263271

272+
@property
273+
def symbol_rate(self) -> float:
274+
r"""
275+
The symbol rate $f_{sym}$ in symbols/s.
276+
"""
277+
return self._symbol_rate
278+
264279
@property
265280
def bits_per_symbol(self) -> int:
266281
r"""
267282
The number of coded bits per symbol $k = \log_2 M$.
268283
"""
269284
return self._bits_per_symbol
270285

286+
@property
287+
def bit_rate(self) -> float:
288+
r"""
289+
The bit rate $f_{b}$ in bits/s.
290+
"""
291+
return self.symbol_rate * self.bits_per_symbol
292+
293+
@property
294+
def samples_per_symbol(self) -> int:
295+
r"""
296+
The number of samples per symbol $f_s / f_{sym}$.
297+
"""
298+
return self._samples_per_symbol
299+
300+
@property
301+
def sample_rate(self) -> float:
302+
r"""
303+
The sample rate $f_s$ in samples/s.
304+
"""
305+
return self.symbol_rate * self.samples_per_symbol
306+
271307
@property
272308
def index(self) -> float:
273309
r"""
@@ -285,13 +321,6 @@ def phase_offset(self) -> float:
285321
"""
286322
return self._phase_offset
287323

288-
@property
289-
def samples_per_symbol(self) -> int:
290-
r"""
291-
The number of samples per symbol $f_s / f_{sym}$.
292-
"""
293-
return self._samples_per_symbol
294-
295324
@property
296325
def pulse_shape(self) -> np.ndarray:
297326
r"""

src/sdr/_modulation/_linear.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def __init__(
4141
self,
4242
symbol_map: npt.ArrayLike,
4343
phase_offset: float = 0.0,
44+
symbol_rate: float = 1.0,
4445
samples_per_symbol: int = 8,
4546
pulse_shape: npt.ArrayLike | Literal["rect", "rc", "srrc"] = "rect",
4647
span: int | None = None,
@@ -54,6 +55,7 @@ def __init__(
5455
are decimal symbols $s[k]$ and whose values are complex symbols $a[k]$, where $M$ is the
5556
modulation order.
5657
phase_offset: A phase offset $\phi$ in degrees to apply to `symbol_map`.
58+
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
5759
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
5860
pulse_shape: The pulse shape $h[n]$ of the modulated signal.
5961
@@ -83,6 +85,12 @@ def __init__(
8385
raise TypeError(f"Argument 'phase_offset' must be a number, not {type(phase_offset)}.")
8486
self._phase_offset = phase_offset # Phase offset in degrees
8587

88+
if not isinstance(symbol_rate, (int, float)):
89+
raise TypeError(f"Argument 'symbol_rate' must be a number, not {type(symbol_rate)}.")
90+
if not symbol_rate > 0:
91+
raise ValueError(f"Argument 'symbol_rate' must be positive, not {symbol_rate}.")
92+
self._symbol_rate = symbol_rate # symbols/s
93+
8694
if not isinstance(samples_per_symbol, int):
8795
raise TypeError(f"Argument 'samples_per_symbol' must be an integer, not {type(samples_per_symbol)}.")
8896
if not samples_per_symbol > 1:
@@ -277,13 +285,41 @@ def order(self) -> int:
277285
"""
278286
return self._order
279287

288+
@property
289+
def symbol_rate(self) -> float:
290+
r"""
291+
The symbol rate $f_{sym}$ in symbols/s.
292+
"""
293+
return self._symbol_rate
294+
280295
@property
281296
def bits_per_symbol(self) -> int:
282297
r"""
283298
The number of coded bits per symbol $k = \log_2 M$.
284299
"""
285300
return self._bits_per_symbol
286301

302+
@property
303+
def bit_rate(self) -> float:
304+
r"""
305+
The bit rate $f_{b}$ in bits/s.
306+
"""
307+
return self.symbol_rate * self.bits_per_symbol
308+
309+
@property
310+
def samples_per_symbol(self) -> int:
311+
r"""
312+
The number of samples per symbol $f_s / f_{sym}$.
313+
"""
314+
return self._samples_per_symbol
315+
316+
@property
317+
def sample_rate(self) -> float:
318+
r"""
319+
The sample rate $f_s$ in samples/s.
320+
"""
321+
return self.symbol_rate * self.samples_per_symbol
322+
287323
@property
288324
def phase_offset(self) -> float:
289325
r"""
@@ -300,13 +336,6 @@ def symbol_map(self) -> npt.NDArray[np.complex128]:
300336
"""
301337
return self._symbol_map
302338

303-
@property
304-
def samples_per_symbol(self) -> int:
305-
r"""
306-
The number of samples per symbol $f_s / f_{sym}$.
307-
"""
308-
return self._samples_per_symbol
309-
310339
@property
311340
def pulse_shape(self) -> npt.NDArray[np.float64]:
312341
r"""

src/sdr/_modulation/_msk.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ def __init__(
137137
self,
138138
phase_offset: float = 45,
139139
symbol_labels: Literal["bin", "gray"] | npt.ArrayLike = "gray",
140+
symbol_rate: float = 1.0,
140141
samples_per_symbol: int = 8,
141142
):
142143
r"""
@@ -152,6 +153,7 @@ def __init__(
152153
the new symbol labels. The default symbol labels are $0$ to $4-1$ for phases starting at $1 + 0j$
153154
and going counter-clockwise around the unit circle.
154155
156+
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
155157
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
156158
157159
See Also:
@@ -162,6 +164,7 @@ def __init__(
162164
super().__init__(
163165
phase_offset=phase_offset,
164166
symbol_labels=symbol_labels,
167+
symbol_rate=symbol_rate,
165168
samples_per_symbol=samples_per_symbol,
166169
pulse_shape=pulse_shape,
167170
)

src/sdr/_modulation/_psk.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ def __init__(
137137
order: int,
138138
phase_offset: float = 0.0,
139139
symbol_labels: Literal["bin", "gray"] | npt.ArrayLike = "gray",
140+
symbol_rate: float = 1.0,
140141
samples_per_symbol: int = 8,
141142
pulse_shape: npt.ArrayLike | Literal["rect", "rc", "srrc"] = "rect",
142143
span: int | None = None,
@@ -156,6 +157,7 @@ def __init__(
156157
the new symbol labels. The default symbol labels are $0$ to $M-1$ for phases starting at $1 + 0j$
157158
and going counter-clockwise around the unit circle.
158159
160+
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
159161
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
160162
pulse_shape: The pulse shape $h[n]$ of the modulated signal.
161163
@@ -178,6 +180,7 @@ def __init__(
178180
super().__init__(
179181
base_symbol_map,
180182
phase_offset=phase_offset,
183+
symbol_rate=symbol_rate,
181184
samples_per_symbol=samples_per_symbol,
182185
pulse_shape=pulse_shape,
183186
span=span,
@@ -591,6 +594,7 @@ def __init__(
591594
order: int,
592595
phase_offset: float = 0.0,
593596
symbol_labels: Literal["bin", "gray"] | npt.ArrayLike = "gray",
597+
symbol_rate: float = 1.0,
594598
samples_per_symbol: int = 8,
595599
pulse_shape: npt.ArrayLike | Literal["rect", "rc", "srrc"] = "rect",
596600
span: int | None = None,
@@ -610,6 +614,7 @@ def __init__(
610614
the new symbol labels. The default symbol labels are $0$ to $M-1$ for phases starting at $1 + 0j$
611615
and going counter-clockwise around the unit circle.
612616
617+
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
613618
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
614619
pulse_shape: The pulse shape $h[n]$ of the modulated signal.
615620
@@ -630,6 +635,7 @@ def __init__(
630635
order,
631636
phase_offset=phase_offset,
632637
symbol_labels=symbol_labels,
638+
symbol_rate=symbol_rate,
633639
samples_per_symbol=samples_per_symbol,
634640
pulse_shape=pulse_shape,
635641
)
@@ -783,6 +789,7 @@ def __init__(
783789
self,
784790
phase_offset: float = 45,
785791
symbol_labels: Literal["bin", "gray"] | npt.ArrayLike = "gray",
792+
symbol_rate: float = 1.0,
786793
samples_per_symbol: int = 8,
787794
pulse_shape: npt.ArrayLike | Literal["rect", "rc", "srrc"] = "rect",
788795
span: int | None = None,
@@ -801,6 +808,7 @@ def __init__(
801808
the new symbol labels. The default symbol labels are $0$ to $4-1$ for phases starting at $1 + 0j$
802809
and going counter-clockwise around the unit circle.
803810
811+
symbol_rate: The symbol rate $f_{sym}$ in symbols/s.
804812
samples_per_symbol: The number of samples per symbol $f_s / f_{sym}$.
805813
pulse_shape: The pulse shape $h[n]$ of the modulated signal.
806814
@@ -821,6 +829,7 @@ def __init__(
821829
4,
822830
phase_offset=phase_offset,
823831
symbol_labels=symbol_labels,
832+
symbol_rate=symbol_rate,
824833
samples_per_symbol=samples_per_symbol,
825834
pulse_shape=pulse_shape,
826835
span=span,

0 commit comments

Comments
 (0)