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.
- pymodulation-0.2.0/AUTHORS +9 -0
- {pymodulation-0.1.0 → pymodulation-0.2.0}/PKG-INFO +34 -12
- {pymodulation-0.1.0 → pymodulation-0.2.0}/README.md +30 -9
- {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation/__init__.py +1 -0
- pymodulation-0.2.0/pymodulation/bpsk.py +109 -0
- pymodulation-0.2.0/pymodulation/gfsk.py +330 -0
- {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation/gmsk.py +4 -3
- pymodulation-0.2.0/pymodulation/modulation.py +75 -0
- {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation/version.py +3 -3
- {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation.egg-info/PKG-INFO +34 -12
- {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation.egg-info/SOURCES.txt +4 -7
- {pymodulation-0.1.0 → pymodulation-0.2.0}/setup.py +2 -2
- pymodulation-0.2.0/tests/test_bpsk.py +235 -0
- {pymodulation-0.1.0 → pymodulation-0.2.0}/tests/test_gfsk.py +29 -14
- {pymodulation-0.1.0 → pymodulation-0.2.0}/tests/test_gmsk.py +7 -6
- pymodulation-0.1.0/pymodulation/ax100.py +0 -286
- pymodulation-0.1.0/pymodulation/bit_buffer.py +0 -72
- pymodulation-0.1.0/pymodulation/bit_decoder.py +0 -107
- pymodulation-0.1.0/pymodulation/byte_buffer.py +0 -66
- pymodulation-0.1.0/pymodulation/gfsk.py +0 -305
- pymodulation-0.1.0/pymodulation/golay24.py +0 -184
- pymodulation-0.1.0/pymodulation/reed_solomon.py +0 -321
- pymodulation-0.1.0/pymodulation/sync_word.py +0 -76
- {pymodulation-0.1.0 → pymodulation-0.2.0}/LICENSE +0 -0
- {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation/pymodulation.py +0 -0
- {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation.egg-info/dependency_links.txt +0 -0
- {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation.egg-info/requires.txt +0 -0
- {pymodulation-0.1.0 → pymodulation-0.2.0}/pymodulation.egg-info/top_level.txt +0 -0
- {pymodulation-0.1.0 → pymodulation-0.2.0}/setup.cfg +0 -0
- {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.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: PyModulation library
|
|
5
|
-
Home-page: https://github.com/
|
|
6
|
-
Download-URL: https://github.com/
|
|
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://
|
|
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/
|
|
61
|
-
<img src="https://img.shields.io/github/license/
|
|
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/
|
|
64
|
-
<img src="https://img.shields.io/github/actions/workflow/status/
|
|
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://
|
|
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/
|
|
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://
|
|
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/
|
|
12
|
-
<img src="https://img.shields.io/github/license/
|
|
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/
|
|
15
|
-
<img src="https://img.shields.io/github/actions/workflow/status/
|
|
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://
|
|
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/
|
|
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.
|
|
@@ -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.
|