pymodulation 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. pymodulation-0.2.0/AUTHORS +9 -0
  2. {pymodulation-0.1.0 → pymodulation-0.2.0}/PKG-INFO +34 -12
  3. {pymodulation-0.1.0 → pymodulation-0.2.0}/README.md +30 -9
  4. {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation/__init__.py +1 -0
  5. pymodulation-0.2.0/pymodulation/bpsk.py +109 -0
  6. pymodulation-0.2.0/pymodulation/gfsk.py +330 -0
  7. {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation/gmsk.py +4 -3
  8. pymodulation-0.2.0/pymodulation/modulation.py +75 -0
  9. {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation/version.py +3 -3
  10. {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation.egg-info/PKG-INFO +34 -12
  11. {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation.egg-info/SOURCES.txt +4 -7
  12. {pymodulation-0.1.0 → pymodulation-0.2.0}/setup.py +2 -2
  13. pymodulation-0.2.0/tests/test_bpsk.py +235 -0
  14. {pymodulation-0.1.0 → pymodulation-0.2.0}/tests/test_gfsk.py +29 -14
  15. {pymodulation-0.1.0 → pymodulation-0.2.0}/tests/test_gmsk.py +7 -6
  16. pymodulation-0.1.0/pymodulation/ax100.py +0 -286
  17. pymodulation-0.1.0/pymodulation/bit_buffer.py +0 -72
  18. pymodulation-0.1.0/pymodulation/bit_decoder.py +0 -107
  19. pymodulation-0.1.0/pymodulation/byte_buffer.py +0 -66
  20. pymodulation-0.1.0/pymodulation/gfsk.py +0 -305
  21. pymodulation-0.1.0/pymodulation/golay24.py +0 -184
  22. pymodulation-0.1.0/pymodulation/reed_solomon.py +0 -321
  23. pymodulation-0.1.0/pymodulation/sync_word.py +0 -76
  24. {pymodulation-0.1.0 → pymodulation-0.2.0}/LICENSE +0 -0
  25. {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation/pymodulation.py +0 -0
  26. {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation.egg-info/dependency_links.txt +0 -0
  27. {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation.egg-info/requires.txt +0 -0
  28. {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation.egg-info/top_level.txt +0 -0
  29. {pymodulation-0.1.0 → pymodulation-0.2.0}/setup.cfg +0 -0
  30. {pymodulation-0.1.0 → pymodulation-0.2.0}/tests/__init__.py +0 -0
@@ -0,0 +1,9 @@
1
+ # This is the list of PyModulation's significant contributors.
2
+ #
3
+ # This does not necessarily list everyone who has contributed code,
4
+ # especially since many members of one organization may be contributing.
5
+ # To see the full list of contributors, see the revision history in
6
+ # source control.
7
+
8
+ Gabriel Mariano Marcelino <gabriel.mm8@gmail.com>
9
+ Carlos Augusto Porto Freitas <carlos.portof@hotmail.com>
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymodulation
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: PyModulation library
5
- Home-page: https://github.com/mgm8/pymodulation
6
- Download-URL: https://github.com/mgm8/pymodulation/releases
5
+ Home-page: https://github.com/pymodulation/pymodulation
6
+ Download-URL: https://github.com/pymodulation/pymodulation/releases
7
7
  Author: Gabriel Mariano Marcelino
8
8
  Author-email: gabriel.mm8@gmail.com
9
9
  Maintainer: Gabriel Mariano Marcelino
@@ -30,6 +30,7 @@ Classifier: Topic :: Software Development :: Libraries
30
30
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
31
31
  Description-Content-Type: text/markdown
32
32
  License-File: LICENSE
33
+ License-File: AUTHORS
33
34
  Requires-Dist: numpy
34
35
  Requires-Dist: scipy
35
36
  Dynamic: author
@@ -48,20 +49,23 @@ Dynamic: requires-dist
48
49
  Dynamic: summary
49
50
 
50
51
  <h1 align="center">
51
- <a href="https://mgm8.github.io/pymodulation/"><img src="docs/img/logo.jpg" alt="PyModulation" width="50%"></a>
52
+ <a href="https://pymodulation.github.io/pymodulation/"><img src="docs/img/logo.jpg" alt="PyModulation" width="50%"></a>
52
53
  </h1>
53
54
 
54
55
  <a href="https://pypi.org/project/pymodulation/">
55
56
  <img src="https://img.shields.io/pypi/v/pymodulation?style=for-the-badge">
56
57
  </a>
58
+ <a href="https://doi.org/10.5281/zenodo.18202698">
59
+ <img src="https://img.shields.io/badge/DOI-10.5281%2Fzenodo.18202698-blue?style=for-the-badge">
60
+ </a>
57
61
  <a href="https://pypi.org/project/pymodulation/">
58
62
  <img src="https://img.shields.io/pypi/pyversions/pymodulation?style=for-the-badge">
59
63
  </a>
60
- <a href="https://github.com/mgm8/pymodulation/blob/main/LICENSE">
61
- <img src="https://img.shields.io/github/license/mgm8/pymodulation?style=for-the-badge">
64
+ <a href="https://github.com/pymodulation/pymodulation/blob/main/LICENSE">
65
+ <img src="https://img.shields.io/github/license/pymodulation/pymodulation?style=for-the-badge">
62
66
  </a>
63
- <a href="https://github.com/mgm8/pymodulation/actions">
64
- <img src="https://img.shields.io/github/actions/workflow/status/mgm8/pymodulation/test.yml?style=for-the-badge">
67
+ <a href="https://github.com/pymodulation/pymodulation/actions">
68
+ <img src="https://img.shields.io/github/actions/workflow/status/pymodulation/pymodulation/test.yml?style=for-the-badge">
65
69
  </a>
66
70
 
67
71
  ## Overview
@@ -75,11 +79,12 @@ PyModulation is suitable for a wide range of applications, including SDR-based t
75
79
  The following modulations are currently supported:
76
80
 
77
81
  * GFSK/GMSK
82
+ * BPSK
78
83
 
79
84
  ## Dependencies
80
85
 
81
- * NumPy
82
- * SciPy
86
+ * [NumPy](https://numpy.org/)
87
+ * [SciPy](https://scipy.org/)
83
88
 
84
89
  ## Installing
85
90
 
@@ -89,9 +94,9 @@ This library can be installed directly from the source files:
89
94
 
90
95
  ## Documentation
91
96
 
92
- The documentation page is available [here](https://mgm8.github.io/pymodulation/). Instructions to build the documentation page are described below.
97
+ The documentation page is available [here](https://pymodulation.github.io/pymodulation/). Instructions to build the documentation page are described below.
93
98
 
94
- Contributing instructions are also available [here](https://github.com/mgm8/pyngham/blob/main/CONTRIBUTING.md).
99
+ Contributing instructions are also available [here](https://github.com/pymodulation/pyngham/blob/main/CONTRIBUTING.md).
95
100
 
96
101
  ### Dependencies
97
102
 
@@ -103,6 +108,23 @@ The documentation pages can be built with Sphinx by running the following comman
103
108
 
104
109
  * ```make html```
105
110
 
111
+ ## Citing
112
+
113
+ If you use PyModulation in your academic work or project, you can cite it:
114
+
115
+ ```
116
+ @misc{marcelino2026,
117
+ author = {Marcelino, Gabriel Mariano},
118
+ title = {PyModulation},
119
+ month = jan,
120
+ year = 2026,
121
+ publisher = {Zenodo},
122
+ version = {v0.1},
123
+ doi = {10.5281/zenodo.18202698},
124
+ url = {https://doi.org/10.5281/zenodo.18202698},
125
+ }
126
+ ```
127
+
106
128
  ## License
107
129
 
108
130
  This project is licensed under LGPLv3 license.
@@ -1,18 +1,21 @@
1
1
  <h1 align="center">
2
- <a href="https://mgm8.github.io/pymodulation/"><img src="docs/img/logo.jpg" alt="PyModulation" width="50%"></a>
2
+ <a href="https://pymodulation.github.io/pymodulation/"><img src="docs/img/logo.jpg" alt="PyModulation" width="50%"></a>
3
3
  </h1>
4
4
 
5
5
  <a href="https://pypi.org/project/pymodulation/">
6
6
  <img src="https://img.shields.io/pypi/v/pymodulation?style=for-the-badge">
7
7
  </a>
8
+ <a href="https://doi.org/10.5281/zenodo.18202698">
9
+ <img src="https://img.shields.io/badge/DOI-10.5281%2Fzenodo.18202698-blue?style=for-the-badge">
10
+ </a>
8
11
  <a href="https://pypi.org/project/pymodulation/">
9
12
  <img src="https://img.shields.io/pypi/pyversions/pymodulation?style=for-the-badge">
10
13
  </a>
11
- <a href="https://github.com/mgm8/pymodulation/blob/main/LICENSE">
12
- <img src="https://img.shields.io/github/license/mgm8/pymodulation?style=for-the-badge">
14
+ <a href="https://github.com/pymodulation/pymodulation/blob/main/LICENSE">
15
+ <img src="https://img.shields.io/github/license/pymodulation/pymodulation?style=for-the-badge">
13
16
  </a>
14
- <a href="https://github.com/mgm8/pymodulation/actions">
15
- <img src="https://img.shields.io/github/actions/workflow/status/mgm8/pymodulation/test.yml?style=for-the-badge">
17
+ <a href="https://github.com/pymodulation/pymodulation/actions">
18
+ <img src="https://img.shields.io/github/actions/workflow/status/pymodulation/pymodulation/test.yml?style=for-the-badge">
16
19
  </a>
17
20
 
18
21
  ## Overview
@@ -26,11 +29,12 @@ PyModulation is suitable for a wide range of applications, including SDR-based t
26
29
  The following modulations are currently supported:
27
30
 
28
31
  * GFSK/GMSK
32
+ * BPSK
29
33
 
30
34
  ## Dependencies
31
35
 
32
- * NumPy
33
- * SciPy
36
+ * [NumPy](https://numpy.org/)
37
+ * [SciPy](https://scipy.org/)
34
38
 
35
39
  ## Installing
36
40
 
@@ -40,9 +44,9 @@ This library can be installed directly from the source files:
40
44
 
41
45
  ## Documentation
42
46
 
43
- The documentation page is available [here](https://mgm8.github.io/pymodulation/). Instructions to build the documentation page are described below.
47
+ The documentation page is available [here](https://pymodulation.github.io/pymodulation/). Instructions to build the documentation page are described below.
44
48
 
45
- Contributing instructions are also available [here](https://github.com/mgm8/pyngham/blob/main/CONTRIBUTING.md).
49
+ Contributing instructions are also available [here](https://github.com/pymodulation/pyngham/blob/main/CONTRIBUTING.md).
46
50
 
47
51
  ### Dependencies
48
52
 
@@ -54,6 +58,23 @@ The documentation pages can be built with Sphinx by running the following comman
54
58
 
55
59
  * ```make html```
56
60
 
61
+ ## Citing
62
+
63
+ If you use PyModulation in your academic work or project, you can cite it:
64
+
65
+ ```
66
+ @misc{marcelino2026,
67
+ author = {Marcelino, Gabriel Mariano},
68
+ title = {PyModulation},
69
+ month = jan,
70
+ year = 2026,
71
+ publisher = {Zenodo},
72
+ version = {v0.1},
73
+ doi = {10.5281/zenodo.18202698},
74
+ url = {https://doi.org/10.5281/zenodo.18202698},
75
+ }
76
+ ```
77
+
57
78
  ## License
58
79
 
59
80
  This project is licensed under LGPLv3 license.
@@ -22,5 +22,6 @@
22
22
 
23
23
  from pymodulation.version import __version__
24
24
 
25
+ from pymodulation.bpsk import BPSK
25
26
  from pymodulation.gfsk import GFSK
26
27
  from pymodulation.gmsk import GMSK
@@ -0,0 +1,109 @@
1
+ #
2
+ # bpsk.py
3
+ #
4
+ # Copyright The PyModulation Contributors.
5
+ #
6
+ # This file is part of PyModulation library.
7
+ #
8
+ # PyModulation library is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Lesser General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # PyModulation library is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Lesser General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public License
19
+ # along with PyModulation library. If not, see <http://www.gnu.org/licenses/>.
20
+ #
21
+ #
22
+
23
+ import numpy as np
24
+ from scipy.signal import upfirdn
25
+
26
+ from pymodulation.modulation import Modulation
27
+
28
+ _BPSK_DEFAULT_OVERSAMPLING_FACTOR = 100
29
+
30
+ class BPSK(Modulation):
31
+ """
32
+ BPSK modulator/demodulator.
33
+ """
34
+
35
+ def modulate(self, data: list, L=_BPSK_DEFAULT_OVERSAMPLING_FACTOR):
36
+ """
37
+ Modulate data into BPSK IQ samples (baseband).
38
+
39
+ :param data: List of integers with the data bytes.
40
+ :type: list
41
+
42
+ :param L: Oversampling factor (Tb/Ts)
43
+ :type: int
44
+
45
+ :return: Tuple of (IQ samples, sample rate in Hz, transmission duration in seconds)
46
+ :rtype: tuple(np.ndarray, float, float)
47
+ """
48
+ s_bb, t = self.modulate_time_domain(data, L)
49
+ samples = s_bb.astype(np.complex64) # BPSK is purely real at baseband
50
+
51
+ f_sym = self.get_baudrate() # Symbol rate in baud
52
+ fs = L * f_sym # Sample rate in Hz
53
+
54
+ n_bits = len(data) * 8
55
+ dur = n_bits / f_sym # Transmission duration in seconds
56
+
57
+ return samples, fs, dur
58
+
59
+ def modulate_time_domain(self, data, L=_BPSK_DEFAULT_OVERSAMPLING_FACTOR):
60
+ """
61
+ Generates the BPSK modulated signal in time domain (baseband).
62
+
63
+ :param data: List of integers with the data bytes.
64
+ :type: list
65
+
66
+ :param L: Oversampling factor (Tb/Ts)
67
+ :type: int
68
+
69
+ :return: Baseband signal in time domain (length N*L).
70
+ :rtype: np.ndarray
71
+
72
+ :return: Discrete time base (length N*L).
73
+ :rtype: np.ndarray
74
+ """
75
+ # Convert to array of bits
76
+ bits = np.array(self._int_list_to_bit_list(data))
77
+ n_bits = len(bits)
78
+
79
+ # NRZ encoder: upfirdn with h=[1]*L produces output of length N*L + L-1
80
+ s_bb_full = upfirdn(h=[1] * L, x=2 * bits - 1, up=L)
81
+
82
+ s_bb = s_bb_full[:n_bits * L]
83
+
84
+ t = np.arange(start=0, stop=n_bits * L) # Discrete time base
85
+
86
+ return s_bb, t
87
+
88
+ def demodulate(self, samples: np.ndarray, fs) -> list:
89
+ """
90
+ Demodulate BPSK IQ samples into bits.
91
+
92
+ :param samples: IQ samples.
93
+ :type: np.ndarray
94
+
95
+ :param fs: Sample rate in S/s
96
+ :type: int
97
+
98
+ :return: Demodulated bits (0 or 1).
99
+ :rtype: list
100
+ """
101
+ L = int(fs/self.get_baudrate()) # Oversampling factor (Samples/bit)
102
+ x = np.real(samples) # I arm
103
+ x = np.convolve(x, np.ones(L)) # Integrate for Tb duration (L samples)
104
+
105
+ x = x[L - 1::L] # Sample at the end of each integration window
106
+
107
+ bits = (x > 0).transpose() # Threshold detector
108
+
109
+ return list(map(int, bits))
@@ -0,0 +1,330 @@
1
+ #
2
+ # gfsk.py
3
+ #
4
+ # Copyright The PyModulation Contributors.
5
+ #
6
+ # This file is part of PyModulation library.
7
+ #
8
+ # PyModulation library is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Lesser General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # PyModulation library is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Lesser General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public License
19
+ # along with PyModulation library. If not, see <http://www.gnu.org/licenses/>.
20
+ #
21
+ #
22
+
23
+ import numpy as np
24
+ from scipy.signal import upfirdn, lfilter
25
+
26
+ from pymodulation.modulation import Modulation
27
+
28
+ _GFSK_DEFAULT_OVERSAMPLING_FACTOR = 100
29
+
30
+ class GFSK(Modulation):
31
+ """
32
+ GFSK modulator/demodulator.
33
+ """
34
+
35
+ def __init__(self, modidx, bt, baud):
36
+ """
37
+ Class constructor with modulation initialization.
38
+
39
+ :param modidx: Modulation index.
40
+ :type: float
41
+
42
+ :param bt: BT product (bandwidth x bit period) for GFSK.
43
+ :type: float
44
+
45
+ :param baud: The desired data rate in bps.
46
+ :type: int
47
+
48
+ :return None
49
+ """
50
+ super().__init__(baud)
51
+
52
+ self._mod_index = float()
53
+ self._bt = float()
54
+
55
+ self.set_modulation_index(modidx)
56
+ self.set_bt(bt)
57
+
58
+ def set_modulation_index(self, modidx):
59
+ """
60
+ Sets the modulation index.
61
+
62
+ :param modidx: The new modulation index.
63
+ :type: float
64
+
65
+ :return: None.
66
+ """
67
+ self._mod_index = modidx
68
+
69
+ def get_modulation_index(self):
70
+ """
71
+ Gets the modulation index.
72
+
73
+ :return: The configured modulation index.
74
+ :rtype: float
75
+ """
76
+ return self._mod_index
77
+
78
+ def set_bt(self, bt):
79
+ """
80
+ Sets the bandwidth-time index.
81
+
82
+ :param bt: The new bandwidth-time product.
83
+ :type: float
84
+
85
+ :return: None
86
+ """
87
+ self._bt = bt
88
+
89
+ def get_bt(self):
90
+ """
91
+ Gets the current bandwidth-time index.
92
+
93
+ :return: The configured bandwidth-time product.
94
+ :rtype: float
95
+ """
96
+ return self._bt
97
+
98
+ def modulate(self, data, sps=_GFSK_DEFAULT_OVERSAMPLING_FACTOR):
99
+ """
100
+ Function to modulate an integer stream using GFSK modulation.
101
+
102
+ :param data: input integer list to modulate (bytes as integers)
103
+ :type: list
104
+
105
+ :param sps: Samples per symbol.
106
+ :type: int
107
+
108
+ :return: s_complex: baseband GFSK signal (I+jQ).
109
+ :rtype: np.ndarray
110
+
111
+ :return: samp: Sample rate in S/s.
112
+ :rtype: int
113
+
114
+ :return: dur: Signal duration in seconds.
115
+ :rtype: float
116
+ """
117
+ I, Q, fs, dur = self.get_iq(data, sps)
118
+ s_complex = I + 1j * Q # Complex baseband representation
119
+
120
+ return s_complex, fs, dur
121
+
122
+ def get_iq(self, data, sps=_GFSK_DEFAULT_OVERSAMPLING_FACTOR):
123
+ """
124
+ Computes the IQ data of the GFSK modulated signal.
125
+
126
+ :param data: Input integer list to modulate (bytes as integers).
127
+ :type: list
128
+
129
+ :param sps: Samples per symbol.
130
+ :type: int
131
+
132
+ :return: I: I data of the modulated signal
133
+ :rtype: TODO
134
+
135
+ :return: Q: Q data of the modulated signal
136
+ :rtype: TODO
137
+
138
+ :return: samp: Sample rate in S/s.
139
+ :rtype: int
140
+
141
+ :return: dur: Signal duration in seconds
142
+ :rtype: float
143
+ """
144
+ # Convert to array of bits
145
+ data = self._int_list_to_bit_list(data)
146
+
147
+ data = np.array(data)
148
+
149
+ # Timing parameters
150
+ fc = self.get_baudrate() # Carrier frequency = Data transfer rate in bps
151
+ fs = sps * fc # Sample frequency in Hz
152
+ Ts = np.float64(1.0) / fs # Sample period in seconds
153
+ Tb = sps * Ts # Bit period in seconds
154
+
155
+ c_t = upfirdn(h=[1] * sps, x=2 * data - 1, up=sps) # NRZ pulse train c(t)
156
+ k = 1 # Truncation length for Gaussian LPF
157
+ h_t = self.gaussian_lpf(Tb, sps, k) # Gaussian LPF
158
+ b_t = np.convolve(h_t, c_t, "full") # Convolve c(t) with Gaussian LPF to get b(t)
159
+ bnorm_t = b_t / np.max(np.abs(b_t)) # Normalize the output of Gaussian LPF to +/-1
160
+
161
+ # Integrate to get phase information
162
+ h = np.float64(self.get_modulation_index()) # Modulation index
163
+ phi_t = lfilter(b=[1], a=[1, -1], x=bnorm_t * Ts) * h * np.pi / Tb
164
+ I = np.cos(phi_t)
165
+ Q = np.sin(phi_t) # Cross-correlated baseband I/Q signals
166
+
167
+ # Sampling values
168
+ dur = len(data) * Tb # Transmission duration in seconds
169
+
170
+ return I, Q, fs, dur
171
+
172
+ def modulate_time_domain(self, data, sps=_GFSK_DEFAULT_OVERSAMPLING_FACTOR):
173
+ """
174
+ Generates the GFSK modulated signal in time domain.
175
+
176
+ :param data: Input integer list to modulate (bytes as integers).
177
+ :type: list
178
+
179
+ :param sps: Samples per symbol.
180
+ :type: int
181
+
182
+ :return: s_t: GFSK modulated signal with carrier s(t) (time domain).
183
+ :rtype: TODO
184
+
185
+ :return: samp: Sample rate in S/s.
186
+ :rtype: int
187
+
188
+ :return: dur: Signal duration in seconds.
189
+ :rtype: float
190
+ """
191
+ I, Q, samp, dur = self.get_iq(data, sps)
192
+
193
+ fc = self.get_baudrate() # Carrier frequency = Data transfer rate in bps
194
+ fs = sps * fc
195
+ Ts = 1 / fs
196
+
197
+ t = Ts * np.arange(start=0, stop=len(I)) # Time base for RF carrier
198
+ sI_t = I * np.cos(2 * np.pi * fc * t)
199
+ sQ_t = Q * np.sin(2 * np.pi * fc * t)
200
+ s_t = sI_t - sQ_t # s(t) - GFSK with RF carrier
201
+
202
+ return s_t, t, samp, dur
203
+
204
+ def gaussian_lpf(self, Tb, sps, k):
205
+ """
206
+ Generate filter coefficients of Gaussian low pass filter.
207
+
208
+ :param Tb: Bit period.
209
+ :type: float
210
+
211
+ :param sps: Samples per symbol.
212
+ :type: int
213
+
214
+ :param k: Span length of the pulse (bit interval).
215
+ :type: float
216
+
217
+ :return h_norm: normalized filter coefficients of Gaussian LPF
218
+ :rtype: list
219
+ """
220
+ B = self.get_bt() / Tb # Bandwidth of the filter
221
+
222
+ # Truncated time limits for the filter
223
+ t = np.arange(start=-k * Tb, stop=k * Tb + Tb / sps, step=Tb / sps)
224
+ h = (B * np.sqrt(2 * np.pi / (np.log(2))) * np.exp(-2 * (t * np.pi * B) ** 2 / (np.log(2))))
225
+ h_norm = h / np.sum(h)
226
+
227
+ return h_norm
228
+
229
+ def gaussian_pulse(self, Tb, sps, k):
230
+ """
231
+ Construct the Gaussian pulse shape from gaussian filter impulse response taps.
232
+
233
+ :param Tb: Bit period.
234
+ :type: float
235
+
236
+ :param sps: Samples per symbol.
237
+ :type: int
238
+
239
+ :param k: Span of the gaussian filter impulse response.
240
+ :type: TODO
241
+
242
+ :return: Gaussian pulse shape.
243
+ :rtype: np.ndarray
244
+ """
245
+ h = self.gaussian_lpf(Tb, sps, k)
246
+ nrz = np.ones(sps)
247
+ g = np.convolve(h, nrz, mode="full")
248
+
249
+ return g / np.sum(g)
250
+
251
+ def demodulate(self, iq_samples, fs):
252
+ """
253
+ Perform GFSK demodulation.
254
+
255
+ :param iq_samples: IQ samples.
256
+ :type: np.ndarray
257
+
258
+ :param fs: Sample rate in S/s.
259
+ :type: int
260
+
261
+ :return: The demodulated bitstream.
262
+ :rtype: list
263
+
264
+ :return: The baseband signal in NRZ format.
265
+ :rtype: list
266
+ """
267
+ sps = int(fs / self.get_baudrate())
268
+
269
+ # Applies the gain of quadrature demod following the equation: gain = fs / (2π * deviation), where deviation is: h * baudrate / 2
270
+ gain = fs / (np.pi * self.get_modulation_index() * self.get_baudrate())
271
+
272
+ # Frequency discriminator
273
+ freq_deviation = gain * self._frequency_discriminator(iq_samples)
274
+
275
+ # Apply Gaussian matched filter
276
+ gaussian_filter = self.gaussian_pulse(1 / self.get_baudrate(), sps, 1)
277
+ filtered_signal = np.convolve(freq_deviation, gaussian_filter, mode="same")
278
+
279
+ # Downsample to symbol rate
280
+ sampled_signal = filtered_signal[sps // 2 :: sps]
281
+
282
+ # Decision thresholding
283
+ demodulated_bits = (sampled_signal > 0).astype(int)
284
+
285
+ return list(demodulated_bits), sampled_signal
286
+
287
+ def _frequency_discriminator(self, iq_samples):
288
+ """
289
+ Extract frequency deviations using phase changes in IQ samples.
290
+
291
+ :param iq_samples: IQ samples.
292
+ :type: np.ndarray
293
+
294
+ :return: TODO
295
+ :rtype: TODO
296
+ """
297
+ freq_deviation = np.angle(iq_samples[1:] * np.conj(iq_samples[:-1]))
298
+
299
+ return np.concatenate([[0], freq_deviation]) # Keep length consistent
300
+
301
+ def quadrature_demod_soft(self, samples, fs):
302
+ """
303
+ Non-coherent demodulation based on frequency discrimination.
304
+
305
+ :param samples: Signal IQ samples.
306
+ :type: np.ndarray
307
+
308
+ :param fs: Sample rate in S/s.
309
+ :type: int
310
+
311
+ :return: Soft symbols at the same rate of the input samples (meaning that sps remains the same, useful for time synchronization loop later).
312
+ :rtype: TODO
313
+ """
314
+ iq_samples = np.asarray(samples, dtype=np.complex64)
315
+
316
+ sps = int(fs / self._baudrate)
317
+
318
+ # Applies the gain of quadrature demod following the equation: gain = fs / (2π * deviation), where deviation is: h * baudrate / 2
319
+ gain = fs / (np.pi * self.get_modulation_index() * self.get_baudrate())
320
+
321
+ freq_deviation = gain * self._frequency_discriminator(iq_samples)
322
+
323
+ # Removing DC (mean) from deviation is equivalent to removing residual Carrier Frequency Offset (CFO)
324
+ freq_deviation -= np.mean(freq_deviation)
325
+
326
+ # Apply matched filter
327
+ g = self.gaussian_pulse(1 / self._baudrate, sps, 1)
328
+ soft_symbols = np.convolve(freq_deviation, g, "same")
329
+
330
+ return soft_symbols
@@ -24,16 +24,17 @@ from pymodulation.gfsk import GFSK
24
24
 
25
25
  class GMSK(GFSK):
26
26
  """
27
- GMSK modulator.
27
+ GMSK modulator/demodulator.
28
28
  """
29
+
29
30
  def __init__(self, bt, baud):
30
31
  """
31
32
  Class constructor with modulation initialization.
32
33
 
33
- :param bt: BT product (bandwidth x bit period) for GMSK
34
+ :param bt: BT product (bandwidth x bit period) for GMSK.
34
35
  :type: float
35
36
 
36
- :param baud: The desired data rate in bps
37
+ :param baud: The desired data rate in bps.
37
38
  :type: int
38
39
 
39
40
  :return: None.