From ce0c5f971c2e016e6b19af8ad1595f82f1a1e662 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 20 Oct 2019 11:32:22 -0400 Subject: [PATCH 001/228] Add initial type stubs --- voicechat_modem_dsp/encoders/bitstream.pyi | 11 +++++++++ .../encoders/ecc/hamming_7_4.pyi | 9 ++++++++ .../modulators/modulator_base.pyi | 11 +++++++++ .../modulators/modulator_fsk.py | 23 +++++++++++++++++++ .../modulators/modulator_utils.pyi | 10 ++++++++ 5 files changed, 64 insertions(+) create mode 100644 voicechat_modem_dsp/encoders/bitstream.pyi create mode 100644 voicechat_modem_dsp/encoders/ecc/hamming_7_4.pyi create mode 100644 voicechat_modem_dsp/modulators/modulator_base.pyi create mode 100644 voicechat_modem_dsp/modulators/modulator_fsk.py create mode 100644 voicechat_modem_dsp/modulators/modulator_utils.pyi diff --git a/voicechat_modem_dsp/encoders/bitstream.pyi b/voicechat_modem_dsp/encoders/bitstream.pyi new file mode 100644 index 0000000..2c9f3a7 --- /dev/null +++ b/voicechat_modem_dsp/encoders/bitstream.pyi @@ -0,0 +1,11 @@ +# Type stub for encoders.bitstream (Python 3) + +from typing import Union, Iterator + +readable_bytearr=Union[bytes, bytearray] +writeable_bytearr=Union[bytearray] + +def read_bitstream(bitstream: readable_bytearr, position: int) -> bool: ... +def read_bitstream_iterator(bitstream: readable_bytearr) -> Iterator[bool]: ... +def write_bitstream(bitstream: writeable_bytearr, + position: int, bit: bool) -> None: ... diff --git a/voicechat_modem_dsp/encoders/ecc/hamming_7_4.pyi b/voicechat_modem_dsp/encoders/ecc/hamming_7_4.pyi new file mode 100644 index 0000000..a3c7980 --- /dev/null +++ b/voicechat_modem_dsp/encoders/ecc/hamming_7_4.pyi @@ -0,0 +1,9 @@ +# Stubs for encoders.ecc.hamming_7_4 (Python 3) +# +# NOTE: This dynamically typed stub was automatically generated by stubgen. + +from ..bitstream import readable_bytearr, read_bitstream, write_bitstream +from typing import Any + +def hamming_encode_7_4(bitstream: readable_bytearr) -> bytes: ... +def hamming_decode_7_4(bitstream: readable_bytearr) -> bytes: ... diff --git a/voicechat_modem_dsp/modulators/modulator_base.pyi b/voicechat_modem_dsp/modulators/modulator_base.pyi new file mode 100644 index 0000000..cd2b559 --- /dev/null +++ b/voicechat_modem_dsp/modulators/modulator_base.pyi @@ -0,0 +1,11 @@ +# Stubs for modulators.modulator_base (Python 3) +# +# NOTE: This dynamically typed stub was automatically generated by stubgen. + +from typing import Any + +class Modulator: + @staticmethod + def generate_timearray(dt: Any, sample_count: Any): ... + def modulate(self, data: Any) -> None: ... + def demodulate(self, time_array: Any, datastream: Any) -> None: ... diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py new file mode 100644 index 0000000..6331644 --- /dev/null +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -0,0 +1,23 @@ +from .modulator_base import Modulator + +class FSKModulator(Modulator): + def __init__(self, dt, freq_map, baud): + if baud>=0.5*max(freq_map.values): + raise ValueError("Baud is too high to be modulated "+ + "using given frequencies") + self.dt=dt + self.freq_list=freq_list + self.baud=baud + + @property + def samples_per_symbol(self): + return (1/self.baud)/self.dt + + def modulate(self, data): + timesamples_needed=len(data)*self.samples_per_symbol + time_array=Modulator.generate_time_array(self.dt,timesamples_needed) + current_sin_timestamp=0 + # Incremental addition is a form of integration + # Frequency in the end is the derivative of phase + def demodulate(self, time_array, datastream): + raise NotImplementedError \ No newline at end of file diff --git a/voicechat_modem_dsp/modulators/modulator_utils.pyi b/voicechat_modem_dsp/modulators/modulator_utils.pyi new file mode 100644 index 0000000..6a684f4 --- /dev/null +++ b/voicechat_modem_dsp/modulators/modulator_utils.pyi @@ -0,0 +1,10 @@ +# Stubs for modulators.modulator_utils (Python 3) +# +# NOTE: This dynamically typed stub was automatically generated by stubgen. + +from typing import Any + +def compute_gaussian_window(dt: float, sigma_dt: float): ... +def fred_harris_fir_tap_count(fs: float, transition_width: float, db_attenuation: float) -> int: ... +def lowpass_fir_filter(dt: float, cutoff_low: float, cutoff_high: float, attenuation: float = ...): ... +def linearize_fir(filter: Any) -> None: ... From bf782e0fcf789299e0ed9713ced8f44ad38c5e08 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 26 Oct 2019 11:17:10 -0400 Subject: [PATCH 002/228] Create mypy stub for encode_pad --- voicechat_modem_dsp/encoders/encode_pad.pyi | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 voicechat_modem_dsp/encoders/encode_pad.pyi diff --git a/voicechat_modem_dsp/encoders/encode_pad.pyi b/voicechat_modem_dsp/encoders/encode_pad.pyi new file mode 100644 index 0000000..c9d2dea --- /dev/null +++ b/voicechat_modem_dsp/encoders/encode_pad.pyi @@ -0,0 +1,24 @@ +# Type stub for encoders.encode_pad (Python 3) + +from .bitstream import read_bitstream_iterator, readable_bytearr, writeable_bytearr, write_bitstream +from typing import Any, List, Dict, Callable + +def base_2_encode(bitstream: readable_bytearr) -> List[float]: ... +def base_2_decode(datastream: List[float]) -> readable_bytearr: ... +def base_4_encode(bitstream: readable_bytearr) -> List[float]: ... +def base_4_decode(datastream: List[float]) -> readable_bytearr: ... +def base_8_encode(bitstream: readable_bytearr) -> List[float]: ... +def base_8_decode(datastream: List[float]) -> readable_bytearr: ... +def base_16_encode(bitstream: readable_bytearr) -> List[float]: ... +def base_16_decode(datastream: List[float]) -> readable_bytearr: ... +def base_32_encode(bitstream: readable_bytearr) -> List[float]: ... +def base_32_decode(datastream: List[float]) -> readable_bytearr: ... +def base_64_encode(bitstream: readable_bytearr) -> List[float]: ... +def base_64_decode(datastream: List[float]) -> readable_bytearr: ... +def base_256_encode(bitstream: readable_bytearr) -> List[float]: ... +def base_256_decode(datastream: List[float]) -> readable_bytearr: ... +def make_pad_array(datastream: List[float], pad_len: int): ... +def unpad_array(datastream: List[float]): ... + +encode_function_mappings: Dict[int,Callable[[readable_bytearr], List[float]]] +decode_function_mappings: Dict[int,Callable[[List[float]], readable_bytearr]] From d502b3d1d8e926b663fa2fb135d036af8bc9436a Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 26 Oct 2019 11:24:02 -0400 Subject: [PATCH 003/228] Add mypy typecheck to 3.7 after success --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index e04d716..71935ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,9 @@ install: script: - sh ./run_tests_coverage.sh +after_success: + - if [ $TRAVIS_PYTHON_VERSION = "3.7" ]; then pushd voicechat_modem_dsp; mypy -p encoders -p modulators; popd; fi + after_script: - wget -O codecov_upload "https://codecov.io/bash" - bash codecov_upload From 37dc3c7ba4193198e2b6fca2355912e96cfd706f Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 26 Oct 2019 13:14:25 -0400 Subject: [PATCH 004/228] Create custom script for running mypy checks --- .travis.yml | 4 +--- run_mypy.sh | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 run_mypy.sh diff --git a/.travis.yml b/.travis.yml index 71935ee..6ad5c58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,7 @@ install: script: - sh ./run_tests_coverage.sh - -after_success: - - if [ $TRAVIS_PYTHON_VERSION = "3.7" ]; then pushd voicechat_modem_dsp; mypy -p encoders -p modulators; popd; fi + - if [ $TRAVIS_PYTHON_VERSION = "3.7" ]; then sh ./run_mypy.sh; fi after_script: - wget -O codecov_upload "https://codecov.io/bash" diff --git a/run_mypy.sh b/run_mypy.sh new file mode 100644 index 0000000..1ef68a9 --- /dev/null +++ b/run_mypy.sh @@ -0,0 +1,6 @@ +#!/bin/sh +pushd voicechat_modem_dsp +mypy -p encoders -p modulators +test_status=$? +popd +exit $test_status From f82f5f01bfc924f58eeeda24a976dd10bfab1e27 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 26 Oct 2019 13:18:32 -0400 Subject: [PATCH 005/228] Remove non-POSIX pushd and popd from mypy script --- run_mypy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run_mypy.sh b/run_mypy.sh index 1ef68a9..3e39029 100644 --- a/run_mypy.sh +++ b/run_mypy.sh @@ -1,6 +1,6 @@ #!/bin/sh -pushd voicechat_modem_dsp +cd voicechat_modem_dsp || exit 1 mypy -p encoders -p modulators test_status=$? -popd +cd .. || exit 1 exit $test_status From 986dcf766dcf38e9040de4d4354b0f3dbe1b9b8b Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 26 Oct 2019 13:34:36 -0400 Subject: [PATCH 006/228] Try adding back in Python 3.8 to Travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6ad5c58..9632fe0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ notifications: email: false python: + - "3.8" - "3.7" - "3.4" - "3.6" From e726c86ec6e4c86cf6c047fb0ce5dca292817c3e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 26 Oct 2019 14:38:05 -0400 Subject: [PATCH 007/228] Try installing liblapack-dev to allow scipy building --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9632fe0..a3f56a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,18 @@ language: python notifications: email: false +addons: + apt: + packages: + - liblapack-dev + python: - "3.8" - "3.7" - "3.4" - "3.6" - "3.5" + cache: pip install: From 0370c813bf0b043febf8fa17d94257772e288afd Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 26 Oct 2019 16:15:28 -0400 Subject: [PATCH 008/228] See if pypy3 also works --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index a3f56a6..c6669bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ language: python notifications: email: false +# Need this for now to build SciPy for Python 3.8 addons: apt: packages: @@ -12,6 +13,7 @@ python: - "3.8" - "3.7" - "3.4" + - "pypy3" - "3.6" - "3.5" From ff91ce638bd8dd534ed368eac85d244cb0a07409 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 28 Oct 2019 12:38:30 -0400 Subject: [PATCH 009/228] Make modulator a proper abstract class --- voicechat_modem_dsp/modulators/modulator_base.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index a55d0a2..71a909d 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -1,13 +1,17 @@ import numpy as np +from abc import ABC, abstractmethod + """ Base class for other modulator objects """ -class Modulator(object): +class Modulator(ABC): @staticmethod def generate_timearray(dt, sample_count): return np.arange(0,dt*sample_count,dt) + @abstractmethod def modulate(self, data): raise NotImplementedError + @abstractmethod def demodulate(self, time_array, datastream): raise NotImplementedError From ba88f8c64976e169fbe1bb4f06f0e01edeaa4a98 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 28 Oct 2019 12:38:58 -0400 Subject: [PATCH 010/228] Give types to the generate_timearray static method --- voicechat_modem_dsp/modulators/modulator_base.pyi | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_base.pyi b/voicechat_modem_dsp/modulators/modulator_base.pyi index cd2b559..5784008 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.pyi +++ b/voicechat_modem_dsp/modulators/modulator_base.pyi @@ -1,11 +1,9 @@ # Stubs for modulators.modulator_base (Python 3) -# -# NOTE: This dynamically typed stub was automatically generated by stubgen. from typing import Any class Modulator: @staticmethod - def generate_timearray(dt: Any, sample_count: Any): ... + def generate_timearray(dt: float, sample_count: int): ... def modulate(self, data: Any) -> None: ... def demodulate(self, time_array: Any, datastream: Any) -> None: ... From 432d8999b28232b447f99c81446beb3ede10514f Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 28 Oct 2019 13:49:06 -0400 Subject: [PATCH 011/228] Move samples_per_symbol helper into base modulator class --- voicechat_modem_dsp/modulators/modulator_base.py | 5 +++++ voicechat_modem_dsp/modulators/modulator_fsk.py | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index 71a909d..5c5e56b 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -4,11 +4,16 @@ """ Base class for other modulator objects +Also contains useful helper functions as static methods """ class Modulator(ABC): @staticmethod def generate_timearray(dt, sample_count): return np.arange(0,dt*sample_count,dt) + @staticmethod + def samples_per_symbol(dt, baud): + return (1/baud)/dt + @abstractmethod def modulate(self, data): raise NotImplementedError diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index 6331644..640eb40 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -8,10 +8,6 @@ def __init__(self, dt, freq_map, baud): self.dt=dt self.freq_list=freq_list self.baud=baud - - @property - def samples_per_symbol(self): - return (1/self.baud)/self.dt def modulate(self, data): timesamples_needed=len(data)*self.samples_per_symbol From caec8e753605ab40fb3708c48ec5d0b65604ffea Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 28 Oct 2019 13:49:31 -0400 Subject: [PATCH 012/228] Update base modulator typestub --- voicechat_modem_dsp/modulators/modulator_base.pyi | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_base.pyi b/voicechat_modem_dsp/modulators/modulator_base.pyi index 5784008..240f2ce 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.pyi +++ b/voicechat_modem_dsp/modulators/modulator_base.pyi @@ -1,9 +1,15 @@ # Stubs for modulators.modulator_base (Python 3) +import abc +from abc import ABC, abstractmethod from typing import Any -class Modulator: +class Modulator(ABC, metaclass=abc.ABCMeta): @staticmethod def generate_timearray(dt: float, sample_count: int): ... - def modulate(self, data: Any) -> None: ... - def demodulate(self, time_array: Any, datastream: Any) -> None: ... + @staticmethod + def samples_per_symbol(dt: float, baud: float): ... + @abstractmethod + def modulate(self, data: Any) -> Any: ... + @abstractmethod + def demodulate(self, time_array: Any, datastream: Any) -> Any: ... From 67a4b6ecfea9645d0084fa12e7cc3bef0870d163 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 28 Oct 2019 14:00:13 -0400 Subject: [PATCH 013/228] Add ending newlines to some files --- test/test_bitstream.py | 2 +- test/test_encoders.py | 2 +- voicechat_modem_dsp/encoders/bitstream.py | 2 +- voicechat_modem_dsp/encoders/ecc/hamming_7_4.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_bitstream.py b/test/test_bitstream.py index d845e42..eb887c7 100644 --- a/test/test_bitstream.py +++ b/test/test_bitstream.py @@ -38,4 +38,4 @@ def test_unit_bitstream_range(): def test_unit_write_bitstream_type(): with pytest.raises(TypeError): - write_bitstream(b"Not mutable",0,True) \ No newline at end of file + write_bitstream(b"Not mutable",0,True) diff --git a/test/test_encoders.py b/test/test_encoders.py index e0468b4..02a59b8 100644 --- a/test/test_encoders.py +++ b/test/test_encoders.py @@ -131,4 +131,4 @@ def test_property_base_256_turnaround(): data_bitstream=bytearray((random.getrandbits(8) for _ in range(n))) data_datastream=base_256_encode(data_bitstream) data_bitstream_recover=base_256_decode(data_datastream) - assert bytes(data_bitstream)==data_bitstream_recover \ No newline at end of file + assert bytes(data_bitstream)==data_bitstream_recover diff --git a/voicechat_modem_dsp/encoders/bitstream.py b/voicechat_modem_dsp/encoders/bitstream.py index 26504b9..a482cbb 100644 --- a/voicechat_modem_dsp/encoders/bitstream.py +++ b/voicechat_modem_dsp/encoders/bitstream.py @@ -24,4 +24,4 @@ def write_bitstream(bitstream, position, bit): if bit: bitstream[byteindex] |= shifted_bit else: - bitstream[byteindex] &= ~shifted_bit \ No newline at end of file + bitstream[byteindex] &= ~shifted_bit diff --git a/voicechat_modem_dsp/encoders/ecc/hamming_7_4.py b/voicechat_modem_dsp/encoders/ecc/hamming_7_4.py index 8d2d94d..5f266f7 100644 --- a/voicechat_modem_dsp/encoders/ecc/hamming_7_4.py +++ b/voicechat_modem_dsp/encoders/ecc/hamming_7_4.py @@ -75,4 +75,4 @@ def hamming_decode_7_4(bitstream): write_bitstream(output_arr,output_index+2,bit6) write_bitstream(output_arr,output_index+3,bit7) output_index+=4 - return bytes(output_arr) \ No newline at end of file + return bytes(output_arr) From d6d84642a0856784808a1a0f9aa83da1364a474e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 28 Oct 2019 22:41:38 -0400 Subject: [PATCH 014/228] Minor cleanup of ECC test --- test/test_ecc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_ecc.py b/test/test_ecc.py index 6195fee..18a1f09 100644 --- a/test/test_ecc.py +++ b/test/test_ecc.py @@ -1,5 +1,6 @@ import random +from voicechat_modem_dsp.encoders.bitstream import * from voicechat_modem_dsp.encoders.ecc.hamming_7_4 import * def test_property_corrupt_hamming_nonmangle(): @@ -20,4 +21,4 @@ def test_property_corrupt_hamming_recoverable_err(): write_bitstream(hamming_data_test,index_offset, not read_bitstream(hamming_data_test,index_offset)) recovered_corrected=hamming_decode_7_4(hamming_data_test) - assert data_test==recovered_corrected \ No newline at end of file + assert data_test==recovered_corrected From 41bab10f95bb4fdcd321268de8648c10a5a00276 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 28 Oct 2019 22:43:25 -0400 Subject: [PATCH 015/228] [ci skip] Update gitignore to ignore provisional stubgen pyi files --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a44136a..4016d21 100644 --- a/.gitignore +++ b/.gitignore @@ -104,4 +104,6 @@ venv.bak/ .mypy_cache/ #Custom additions -.vscode/ \ No newline at end of file +.vscode/ +#MyPy stubgen +out/ From 2258e0768b99b7b3996c326a0c848468855b04b7 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 3 Nov 2019 18:39:48 -0500 Subject: [PATCH 016/228] Convert all dt references to fs references Code still not tested and therefore assumed buggy (at least for now) --- voicechat_modem_dsp/modulators/modulator_base.py | 7 ++++--- voicechat_modem_dsp/modulators/modulator_base.pyi | 4 ++-- voicechat_modem_dsp/modulators/modulator_fsk.py | 10 ++++++---- voicechat_modem_dsp/modulators/modulator_utils.py | 8 ++++---- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index 5c5e56b..522752c 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -8,11 +8,12 @@ """ class Modulator(ABC): @staticmethod - def generate_timearray(dt, sample_count): + def generate_timearray(fs, sample_count): + dt=1/fs return np.arange(0,dt*sample_count,dt) @staticmethod - def samples_per_symbol(dt, baud): - return (1/baud)/dt + def samples_per_symbol(fs, baud): + return fs/baud @abstractmethod def modulate(self, data): diff --git a/voicechat_modem_dsp/modulators/modulator_base.pyi b/voicechat_modem_dsp/modulators/modulator_base.pyi index 240f2ce..d875af5 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.pyi +++ b/voicechat_modem_dsp/modulators/modulator_base.pyi @@ -6,9 +6,9 @@ from typing import Any class Modulator(ABC, metaclass=abc.ABCMeta): @staticmethod - def generate_timearray(dt: float, sample_count: int): ... + def generate_timearray(fs: float, sample_count: int): ... @staticmethod - def samples_per_symbol(dt: float, baud: float): ... + def samples_per_symbol(fs: float, baud: float) -> float: ... @abstractmethod def modulate(self, data: Any) -> Any: ... @abstractmethod diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index 640eb40..adc1194 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -1,17 +1,19 @@ from .modulator_base import Modulator class FSKModulator(Modulator): - def __init__(self, dt, freq_map, baud): - if baud>=0.5*max(freq_map.values): + def __init__(self, fs, freq_list, baud): + if baud>=0.5*max(freq_list): raise ValueError("Baud is too high to be modulated "+ "using given frequencies") - self.dt=dt + if fs<=2*max(freq_list): #WROG? + raise ValueError + self.fs=fs self.freq_list=freq_list self.baud=baud def modulate(self, data): timesamples_needed=len(data)*self.samples_per_symbol - time_array=Modulator.generate_time_array(self.dt,timesamples_needed) + time_array=Modulator.generate_time_array(self.fs,timesamples_needed) current_sin_timestamp=0 # Incremental addition is a form of integration # Frequency in the end is the derivative of phase diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 1c9cfb5..c78a011 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -30,10 +30,10 @@ def fred_harris_fir_tap_count(fs, transition_width, db_attenuation): Computes lowpass FIR filter given cutoffs Uses the SciPy implementation of the Remez Exchange Algorithm """ -def lowpass_fir_filter(dt,cutoff_low,cutoff_high,attenuation=80): - tap_count=fred_harris_fir_tap_count(1/dt,cutoff_high-cutoff_low,attenuation) +def lowpass_fir_filter(fs,cutoff_low,cutoff_high,attenuation=80): + tap_count=fred_harris_fir_tap_count(fs,cutoff_high-cutoff_low,attenuation) lowpass_filt=signal.remez(tap_count, - [0,cutoff_low,cutoff_high,0.5/dt],[1,0],fs=1/dt) + [0,cutoff_low,cutoff_high,0.5*fs],[1,0],fs=fs) # TODO: remove zeros? return lowpass_filt @@ -46,4 +46,4 @@ def lowpass_fir_filter(dt,cutoff_low,cutoff_high,attenuation=80): def linearize_fir(fir_filter): frequencies,response=signal.freqz(fir_filter) # TODO fill in the rest of this code if this ends up being actually used - raise NotImplementedError \ No newline at end of file + raise NotImplementedError From 7d6d3201065a50801c6a8effa2bd8f77868d5182 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 5 Nov 2019 15:47:35 -0500 Subject: [PATCH 017/228] Move helper static methods to be functions --- voicechat_modem_dsp/modulators/modulator_base.py | 8 -------- voicechat_modem_dsp/modulators/modulator_base.pyi | 4 ---- voicechat_modem_dsp/modulators/modulator_utils.py | 5 +++++ voicechat_modem_dsp/modulators/modulator_utils.pyi | 6 ++++-- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index 522752c..29d2f12 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -7,14 +7,6 @@ Also contains useful helper functions as static methods """ class Modulator(ABC): - @staticmethod - def generate_timearray(fs, sample_count): - dt=1/fs - return np.arange(0,dt*sample_count,dt) - @staticmethod - def samples_per_symbol(fs, baud): - return fs/baud - @abstractmethod def modulate(self, data): raise NotImplementedError diff --git a/voicechat_modem_dsp/modulators/modulator_base.pyi b/voicechat_modem_dsp/modulators/modulator_base.pyi index d875af5..bc9ab9e 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.pyi +++ b/voicechat_modem_dsp/modulators/modulator_base.pyi @@ -5,10 +5,6 @@ from abc import ABC, abstractmethod from typing import Any class Modulator(ABC, metaclass=abc.ABCMeta): - @staticmethod - def generate_timearray(fs: float, sample_count: int): ... - @staticmethod - def samples_per_symbol(fs: float, baud: float) -> float: ... @abstractmethod def modulate(self, data: Any) -> Any: ... @abstractmethod diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index c78a011..a150854 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -1,6 +1,11 @@ import numpy as np from scipy import signal +def generate_timearray(fs, sample_count): + dt=1/fs + return np.arange(0,dt*sample_count,dt) +def samples_per_symbol(fs, baud): + return fs/baud """ Computes a gaussian smoothing filter given time dt and sigma """ diff --git a/voicechat_modem_dsp/modulators/modulator_utils.pyi b/voicechat_modem_dsp/modulators/modulator_utils.pyi index 6a684f4..3c21cf2 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.pyi +++ b/voicechat_modem_dsp/modulators/modulator_utils.pyi @@ -1,10 +1,12 @@ # Stubs for modulators.modulator_utils (Python 3) -# -# NOTE: This dynamically typed stub was automatically generated by stubgen. from typing import Any +def generate_timearray(fs: float, sample_count: int): ... +def samples_per_symbol(fs: float, baud: float) -> float: ... + def compute_gaussian_window(dt: float, sigma_dt: float): ... def fred_harris_fir_tap_count(fs: float, transition_width: float, db_attenuation: float) -> int: ... def lowpass_fir_filter(dt: float, cutoff_low: float, cutoff_high: float, attenuation: float = ...): ... + def linearize_fir(filter: Any) -> None: ... From 9ed0b1d3eb4150f1207d83cbcc36a366e1979961 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 5 Nov 2019 19:57:46 -0500 Subject: [PATCH 018/228] Remove WROG annotation from FSK --- voicechat_modem_dsp/modulators/modulator_fsk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index adc1194..84ab2ee 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -5,7 +5,7 @@ def __init__(self, fs, freq_list, baud): if baud>=0.5*max(freq_list): raise ValueError("Baud is too high to be modulated "+ "using given frequencies") - if fs<=2*max(freq_list): #WROG? + if fs<=2*max(freq_list): raise ValueError self.fs=fs self.freq_list=freq_list From d17a4917d1e7b5071155e7eb54f7fa10c7194193 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 9 Nov 2019 15:59:25 -0500 Subject: [PATCH 019/228] Convert gaussian window to fs and add docstrings --- .../modulators/modulator_utils.py | 16 +++++++++++++--- .../modulators/modulator_utils.pyi | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index a150854..1e239b9 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -1,16 +1,26 @@ import numpy as np from scipy import signal +""" +Computes a time array given a sampling rate and a sample count +""" def generate_timearray(fs, sample_count): dt=1/fs return np.arange(0,dt*sample_count,dt) + +""" +Computes the number of samples per symbol given a baud and sampling rate + +Note: This rate may not be an integer +""" def samples_per_symbol(fs, baud): return fs/baud + """ -Computes a gaussian smoothing filter given time dt and sigma +Computes a gaussian smoothing filter given sampling rate and sigma time """ -def compute_gaussian_window(dt, sigma_dt): - sigma=sigma_dt/dt +def compute_gaussian_window(fs, sigma_dt): + sigma=sigma_dt*fs sample_count=np.ceil(6*sigma+1) if sample_count%2==0: sample_count+=1 diff --git a/voicechat_modem_dsp/modulators/modulator_utils.pyi b/voicechat_modem_dsp/modulators/modulator_utils.pyi index 3c21cf2..6e53b43 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.pyi +++ b/voicechat_modem_dsp/modulators/modulator_utils.pyi @@ -5,7 +5,7 @@ from typing import Any def generate_timearray(fs: float, sample_count: int): ... def samples_per_symbol(fs: float, baud: float) -> float: ... -def compute_gaussian_window(dt: float, sigma_dt: float): ... +def compute_gaussian_window(fs: float, sigma_dt: float): ... def fred_harris_fir_tap_count(fs: float, transition_width: float, db_attenuation: float) -> int: ... def lowpass_fir_filter(dt: float, cutoff_low: float, cutoff_high: float, attenuation: float = ...): ... From 0e905c7c4472b54f9b676f4589d71963e9727e0a Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 10 Nov 2019 22:49:35 -0500 Subject: [PATCH 020/228] Create integral averaging function in modulator utils --- .../modulators/modulator_utils.py | 37 +++++++++++++++++++ .../modulators/modulator_utils.pyi | 1 + 2 files changed, 38 insertions(+) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 1e239b9..2622bc7 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -16,6 +16,43 @@ def generate_timearray(fs, sample_count): def samples_per_symbol(fs, baud): return fs/baud +""" +Computes the average of the data over the specified interval with integrals. + +The bounds on the given interval need not be integers. +Trapezoidal interpolation is used the average integral formula. + +Note: Due to trapezoidal approximation this will not produce the +normal average if the bounds are integers. In addition to the 1/2 for endpoints, +The endpoints are explicitly included here, unlike for normal array slicing. +""" +def average_interval_data(data, begin, end): + width=end-begin + + # Get bounding indicies + begin_index=int(np.floor(begin)) + begin_frac=begin-begin_index + end_index=int(np.ceil(end))-1 + end_frac=1-end+np.ceil(end) + + # Increment begin_index to exclude start element + # Increment end_index to include second-to-last element but exclude last + sum_interval=sum(data[begin_index+1:end_index+1]) + + # Compute beginning contribution + begin_val=(1-begin_frac)**2 * data[begin_index] \ + + begin_frac*(1-begin_frac)*data[begin_index+1] + begin_val*=0.5 + # Compute ending contribution + end_val=end_frac*(1-end_frac)*data[end_index] \ + + end_frac**2*data[end_index+1] + end_val*=0.5 + + # Add this to the sum and average by dividing out width + sum_inverval+=(begin_val+end_val) + return sum_interval/width + + """ Computes a gaussian smoothing filter given sampling rate and sigma time """ diff --git a/voicechat_modem_dsp/modulators/modulator_utils.pyi b/voicechat_modem_dsp/modulators/modulator_utils.pyi index 6e53b43..1eefca1 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.pyi +++ b/voicechat_modem_dsp/modulators/modulator_utils.pyi @@ -4,6 +4,7 @@ from typing import Any def generate_timearray(fs: float, sample_count: int): ... def samples_per_symbol(fs: float, baud: float) -> float: ... +def average_interval_data(data: Any, begin: float, end: float) -> float: ... def compute_gaussian_window(fs: float, sigma_dt: float): ... def fred_harris_fir_tap_count(fs: float, transition_width: float, db_attenuation: float) -> int: ... From e907ddfffd4489968bf6272e409e2d5ef2d5c706 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 10 Nov 2019 22:50:17 -0500 Subject: [PATCH 021/228] Skeleton of ASK modulator --- .../modulators/modulator_ask.py | 31 +++++++++++++++++++ .../modulators/modulator_ask.pyi | 13 ++++++++ 2 files changed, 44 insertions(+) create mode 100644 voicechat_modem_dsp/modulators/modulator_ask.py create mode 100644 voicechat_modem_dsp/modulators/modulator_ask.pyi diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py new file mode 100644 index 0000000..2494037 --- /dev/null +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -0,0 +1,31 @@ +from .modulator_base import Modulator + +from . import modulator_utils + +import numpy as np + +class ASKModulator(Modulator): + def __init__(self, fs, carrier, amp_list, baud): + if baud>=0.5*carrier: + raise ValueError("Baud is too high to be modulated "+ + "using carrier frequency") + if fs<=2*carrier: + raise ValueError("Carrier frequency is too high for sampling rate") + if any((x>1 or x<0.02 for x in amp_list)): + raise ValueError("Invalid amplitudes given") + self.fs=fs + self.amp_list=dict(enumerate(amp_list)) + self.baud=baud + + def modulate(self, data): + samples_symbol=modulator_utils.samples_per_symbol(self.fs, self.baud) + + amplitude_data=[0,0]+[amp_list[datum] for datum in data]+[0,0] + time_array=Modulator.generate_time_array(self.fs,len(amplitude_data)*samples_symbol) + interpolated_amplitude=np.interp( + np.linspace(0,int(len(data)*2),len(data),endpoint=False), + range(len(amplitude_data)),amplitude_data) + + + def demodulate(self, time_array, datastream): + raise NotImplementedError \ No newline at end of file diff --git a/voicechat_modem_dsp/modulators/modulator_ask.pyi b/voicechat_modem_dsp/modulators/modulator_ask.pyi new file mode 100644 index 0000000..5700e15 --- /dev/null +++ b/voicechat_modem_dsp/modulators/modulator_ask.pyi @@ -0,0 +1,13 @@ +# Stubs for modulators.modulator_ask (Python 3) + +from .modulator_base import Modulator +from typing import Any, List, Dict + +class ASKModulator(Modulator): + fs: float = ... + amp_list: Dict[int, float] = ... + baud: float = ... + def __init__(self, fs: float, carrier: float, + amp_list: List[float], baud: float) -> None: ... + def modulate(self, data: Any) -> None: ... + def demodulate(self, time_array: Any, datastream: Any) -> None: ... From d540d35dd17a3b8a54cdced37d3a9e545816bf60 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 10 Nov 2019 23:28:22 -0500 Subject: [PATCH 022/228] Use linspace instead of arange for timearray generation --- voicechat_modem_dsp/modulators/modulator_utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 2622bc7..f8cd848 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -6,7 +6,7 @@ """ def generate_timearray(fs, sample_count): dt=1/fs - return np.arange(0,dt*sample_count,dt) + return np.linspace(0,dt*sample_count,sample_count,endpoint=False) """ Computes the number of samples per symbol given a baud and sampling rate @@ -37,7 +37,9 @@ def average_interval_data(data, begin, end): # Increment begin_index to exclude start element # Increment end_index to include second-to-last element but exclude last - sum_interval=sum(data[begin_index+1:end_index+1]) + sum_interval=0 + if end_index-begin_index>0: + sum_interval=sum(data[begin_index+1:end_index+1]) # Compute beginning contribution begin_val=(1-begin_frac)**2 * data[begin_index] \ @@ -49,7 +51,7 @@ def average_interval_data(data, begin, end): end_val*=0.5 # Add this to the sum and average by dividing out width - sum_inverval+=(begin_val+end_val) + sum_interval+=(begin_val+end_val) return sum_interval/width From 407ad45efe7bde59e0777abd487fa47108572f8c Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 10 Nov 2019 23:28:36 -0500 Subject: [PATCH 023/228] Write some tests for modulator utils TODO: finish tests --- test/test_modulator_utils.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/test_modulator_utils.py diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py new file mode 100644 index 0000000..d2841c9 --- /dev/null +++ b/test/test_modulator_utils.py @@ -0,0 +1,25 @@ +import random +import numpy as np + +from voicechat_modem_dsp.modulators.modulator_utils import * + +epsilon=1e-14 + +def test_property_generate_timearray(): + for _ in range(256): + n=random.randint(1,50000) + timearr=generate_timearray(n,n+1) + assert timearr[0]==0 + assert all(np.abs(np.diff(timearr)-1/n) < epsilon) + assert np.abs(timearr[-1]-1) < epsilon + +def test_unit_average(): + dataseq=np.asarray([1,1,4,1]) + assert average_interval_data(dataseq,0,3)==2 + + assert average_interval_data(dataseq,0,1)==1 + assert average_interval_data(dataseq,1,2)==2.5 + assert average_interval_data(dataseq,2,3)==2.5 + +def test_property_average_uniform(): + pass \ No newline at end of file From 7b0eb3b4555f7206648a995090e8c18f8c146ff8 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 Nov 2019 15:54:15 -0500 Subject: [PATCH 024/228] Fix average function bugs and add bounds checking --- .../modulators/modulator_utils.py | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index f8cd848..68c82c6 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -27,31 +27,50 @@ def samples_per_symbol(fs, baud): The endpoints are explicitly included here, unlike for normal array slicing. """ def average_interval_data(data, begin, end): + if end(len(data)-1): + raise ValueError("Invalid index range specified") width=end-begin # Get bounding indicies begin_index=int(np.floor(begin)) begin_frac=begin-begin_index end_index=int(np.ceil(end))-1 - end_frac=1-end+np.ceil(end) + end_frac=1-(np.ceil(end)-end) - # Increment begin_index to exclude start element - # Increment end_index to include second-to-last element but exclude last - sum_interval=0 if end_index-begin_index>0: - sum_interval=sum(data[begin_index+1:end_index+1]) - - # Compute beginning contribution - begin_val=(1-begin_frac)**2 * data[begin_index] \ - + begin_frac*(1-begin_frac)*data[begin_index+1] - begin_val*=0.5 - # Compute ending contribution - end_val=end_frac*(1-end_frac)*data[end_index] \ - + end_frac**2*data[end_index+1] - end_val*=0.5 - - # Add this to the sum and average by dividing out width - sum_interval+=(begin_val+end_val) + if end_index-begin_index==1: + # Case 2: two trapezoids with no whole trapezoids in between + # Compute the portion of the trapzeoid normally folded into sum + # Lump sum does not work because the widths are smaller + sum_interval=data[end_index]*(1-begin_frac+end_frac) + sum_interval*=0.5 + else: + # Case 3: general trapezoidal integration + # Increment begin_index to exclude start element + # Increment end_index to include second-to-last element + # but exclude last + sum_interval=sum(data[begin_index+1:end_index+1]) + + # Compute beginning contribution + begin_val=(1-begin_frac)**2 * data[begin_index] \ + + begin_frac*(1-begin_frac)*data[begin_index+1] + begin_val*=0.5 + # Compute ending contribution + end_val=end_frac*(1-end_frac)*data[end_index] \ + + end_frac**2*data[end_index+1] + end_val*=0.5 + + # Add this to the sum and average by dividing out width + sum_interval+=(begin_val+end_val) + else: + # Case 1: A single trapezoid + # begin_index and end_index equal here, but separate for readability + sum_interval=(end_frac-begin_frac)* \ + ((1-begin_frac)*data[begin_index]+begin_frac*data[begin_index+1]+ \ + (1-end_frac)*data[end_index]+end_frac*data[end_index+1]) + sum_interval*=0.5 return sum_interval/width From fcb8a867e8f2685a9288f2615cb0b2ab715ddef5 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 Nov 2019 15:54:23 -0500 Subject: [PATCH 025/228] Write more tests for the average function --- test/test_modulator_utils.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index d2841c9..d23cbdc 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -1,6 +1,8 @@ import random import numpy as np +import pytest + from voicechat_modem_dsp.modulators.modulator_utils import * epsilon=1e-14 @@ -13,7 +15,7 @@ def test_property_generate_timearray(): assert all(np.abs(np.diff(timearr)-1/n) < epsilon) assert np.abs(timearr[-1]-1) < epsilon -def test_unit_average(): +def test_unit_average_int(): dataseq=np.asarray([1,1,4,1]) assert average_interval_data(dataseq,0,3)==2 @@ -21,5 +23,16 @@ def test_unit_average(): assert average_interval_data(dataseq,1,2)==2.5 assert average_interval_data(dataseq,2,3)==2.5 -def test_property_average_uniform(): +def test_unit_average_float(): + dataseq=np.asarray([2,2,2,3,4]) + assert average_interval_data(dataseq,0.5,1.75)==2 + +def test_unit_average_invalid(): + dataseq=[4,1,3,6,1,2,10,2,5] + with pytest.raises(ValueError, match=r".*must be larger than.*"): + average_interval_data(dataseq,4.9,1.6) + with pytest.raises(ValueError, match=r"Invalid index.*"): + average_interval_data(dataseq,-1,28.9) + +def test_property_average_triangle(): pass \ No newline at end of file From 76edb567fc6038e7ce155a9085d48e6f9a4364c9 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 Nov 2019 17:26:59 -0500 Subject: [PATCH 026/228] Add property test for average using triangular area under function --- test/test_modulator_utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index d23cbdc..415a2c0 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -35,4 +35,10 @@ def test_unit_average_invalid(): average_interval_data(dataseq,-1,28.9) def test_property_average_triangle(): - pass \ No newline at end of file + for _ in range(64): + n=random.randint(1,5000) + range_data=list(range(n)) + for _ in range(8): + upper_bound=random.randrange(n-1) + assert average_interval_data(range_data, + 0,upper_bound)==upper_bound/2 From 80bdbdb851f12229bbdae20ead93d92b4ceecf40 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 Nov 2019 18:03:00 -0500 Subject: [PATCH 027/228] Check for special case of width=0 when averaging --- voicechat_modem_dsp/modulators/modulator_utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 68c82c6..e56c6ba 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -32,6 +32,11 @@ def average_interval_data(data, begin, end): if begin<0 or end>(len(data)-1): raise ValueError("Invalid index range specified") width=end-begin + # Handle special case of width=0 + if width==0: + index_int=int(np.floor(begin)) + index_frac=begin-np.floor(begin) + return (1-index_frac)*data[index_int]+index_frac*data[index_int+1] # Get bounding indicies begin_index=int(np.floor(begin)) From 290857051ee47d90d8ef51585d8a0dbf7836b34d Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 Nov 2019 18:03:16 -0500 Subject: [PATCH 028/228] Add test for when bounds are equal --- test/test_modulator_utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index 415a2c0..3d220f0 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -23,6 +23,11 @@ def test_unit_average_int(): assert average_interval_data(dataseq,1,2)==2.5 assert average_interval_data(dataseq,2,3)==2.5 +def test_unit_average_interp(): + dataseq=[3,1,0,5] + assert np.abs(average_interval_data(dataseq,0.5,0.5)-2) Date: Mon, 11 Nov 2019 18:03:32 -0500 Subject: [PATCH 029/228] Increase repetitions in triangle average property test --- test/test_modulator_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index 3d220f0..d30171e 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -40,10 +40,10 @@ def test_unit_average_invalid(): average_interval_data(dataseq,-1,28.9) def test_property_average_triangle(): - for _ in range(64): + for _ in range(256): n=random.randint(1,5000) range_data=list(range(n)) - for _ in range(8): + for _ in range(16): upper_bound=random.randrange(n-1) assert average_interval_data(range_data, 0,upper_bound)==upper_bound/2 From b11565b5d4cb6367deed649286fd4e8fd4129e61 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 Nov 2019 18:59:34 -0500 Subject: [PATCH 030/228] Bum lower bound of range for triangle average test --- test/test_modulator_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index d30171e..ed3880a 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -41,7 +41,7 @@ def test_unit_average_invalid(): def test_property_average_triangle(): for _ in range(256): - n=random.randint(1,5000) + n=random.randint(2,5000) range_data=list(range(n)) for _ in range(16): upper_bound=random.randrange(n-1) From 5357e26efbb158cbd7c4b93001b5bac19a619497 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 Nov 2019 22:44:18 -0500 Subject: [PATCH 031/228] Increase minimum amplitude threshold for ASK --- voicechat_modem_dsp/modulators/modulator_ask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 2494037..712dbae 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -11,7 +11,7 @@ def __init__(self, fs, carrier, amp_list, baud): "using carrier frequency") if fs<=2*carrier: raise ValueError("Carrier frequency is too high for sampling rate") - if any((x>1 or x<0.02 for x in amp_list)): + if any((x>1 or x<0.1 for x in amp_list)): raise ValueError("Invalid amplitudes given") self.fs=fs self.amp_list=dict(enumerate(amp_list)) From c48476e7f91368aa494006671eaa3df7bf7ebaf1 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 Nov 2019 22:45:28 -0500 Subject: [PATCH 032/228] Work on data upsampling procedures (no linear interpolation) --- voicechat_modem_dsp/modulators/modulator_ask.py | 11 ++++++----- voicechat_modem_dsp/modulators/modulator_utils.py | 9 +++++++++ voicechat_modem_dsp/modulators/modulator_utils.pyi | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 712dbae..99dd9b8 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -20,11 +20,12 @@ def __init__(self, fs, carrier, amp_list, baud): def modulate(self, data): samples_symbol=modulator_utils.samples_per_symbol(self.fs, self.baud) - amplitude_data=[0,0]+[amp_list[datum] for datum in data]+[0,0] - time_array=Modulator.generate_time_array(self.fs,len(amplitude_data)*samples_symbol) - interpolated_amplitude=np.interp( - np.linspace(0,int(len(data)*2),len(data),endpoint=False), - range(len(amplitude_data)),amplitude_data) + amplitude_data=[0]+[amp_list[datum] for datum in data]+[0] + time_array=Modulator.generate_time_array( + self.fs,len(amplitude_data)*samples_symbol) + interpolated_amplitude=modulator_utils.previous_resample_interpolate( + time_array, self.baud, amplitude_data) + def demodulate(self, time_array, datastream): diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index e56c6ba..a3ee943 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -16,6 +16,15 @@ def generate_timearray(fs, sample_count): def samples_per_symbol(fs, baud): return fs/baud +""" +Maps a sequence (assumed to be sampled at baud rate) to a new timesequence +""" +def previous_resample_interpolate(timeseq, baud, data): + resampled_data=list() + for cumulative_time in timeseq: + resampled_data.append(data[int(np.floor(cumulative_time*baud))]) + return resampled_data + """ Computes the average of the data over the specified interval with integrals. diff --git a/voicechat_modem_dsp/modulators/modulator_utils.pyi b/voicechat_modem_dsp/modulators/modulator_utils.pyi index 1eefca1..84a4307 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.pyi +++ b/voicechat_modem_dsp/modulators/modulator_utils.pyi @@ -4,6 +4,7 @@ from typing import Any def generate_timearray(fs: float, sample_count: int): ... def samples_per_symbol(fs: float, baud: float) -> float: ... +def previous_resample_interpolate(timeseq: Any, baud: float, sequence: Any): ... def average_interval_data(data: Any, begin: float, end: float) -> float: ... def compute_gaussian_window(fs: float, sigma_dt: float): ... From f552da6cf456f085a69ac8fda2d8d878cbc7e850 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 12 Nov 2019 20:54:34 -0500 Subject: [PATCH 033/228] Update docstrings of modulator utils --- voicechat_modem_dsp/modulators/modulator_utils.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index a3ee943..be9f5d6 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -11,13 +11,13 @@ def generate_timearray(fs, sample_count): """ Computes the number of samples per symbol given a baud and sampling rate -Note: This rate may not be an integer +Note: Neither quantity has to be an integer """ def samples_per_symbol(fs, baud): return fs/baud """ -Maps a sequence (assumed to be sampled at baud rate) to a new timesequence +Maps a sequence (assumed to be sampled at baud rate) to a timesequence """ def previous_resample_interpolate(timeseq, baud, data): resampled_data=list() @@ -29,11 +29,13 @@ def previous_resample_interpolate(timeseq, baud, data): Computes the average of the data over the specified interval with integrals. The bounds on the given interval need not be integers. -Trapezoidal interpolation is used the average integral formula. +Linear interpolation is used the average integral formula. Note: Due to trapezoidal approximation this will not produce the -normal average if the bounds are integers. In addition to the 1/2 for endpoints, -The endpoints are explicitly included here, unlike for normal array slicing. +normal average if the bounds are integers. +Both endpoints are explicitly included, unlike normal array slicing. +In addition, the values at the extremities receive half the weight +as the rest of the data, following the trapezoidal integraion formula. """ def average_interval_data(data, begin, end): if end Date: Tue, 12 Nov 2019 22:05:39 -0500 Subject: [PATCH 034/228] Init Sphinx project --- Pipfile | 7 +- Pipfile.lock | 397 ++++++++++++++++++++++++++++++++++++++++++------- docs/Makefile | 20 +++ docs/conf.py | 53 +++++++ docs/index.rst | 20 +++ docs/make.bat | 35 +++++ 6 files changed, 478 insertions(+), 54 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat diff --git a/Pipfile b/Pipfile index f3fcab6..d249014 100644 --- a/Pipfile +++ b/Pipfile @@ -10,5 +10,10 @@ protobuf = "*" [dev-packages] pytest = "*" -mypy = {version="*", python_version=">='3.5'"} coverage = "*" +sphinx = "*" +nbsphinx = "*" + +[dev-packages.mypy] +version = "*" +python_version = ">='3.5'" diff --git a/Pipfile.lock b/Pipfile.lock index f0dca55..7822ca1 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "8e3921939c42fdc41eace9cfa581210463b17a59412169eb2a6f69fff963854d" + "sha256": "07e26997d877719eb5260008b7507fe2bc16edc71272cceb6f8e60652a4d6a30" }, "pipfile-spec": 6, "requires": {}, @@ -16,30 +16,30 @@ "default": { "numpy": { "hashes": [ - "sha256:0b0dd8f47fb177d00fa6ef2d58783c4f41ad3126b139c91dd2f7c4b3fdf5e9a5", - "sha256:25ffe71f96878e1da7e014467e19e7db90ae7d4e12affbc73101bcf61785214e", - "sha256:26efd7f7d755e6ca966a5c0ac5a930a87dbbaab1c51716ac26a38f42ecc9bc4b", - "sha256:28b1180c758abf34a5c3fea76fcee66a87def1656724c42bb14a6f9717a5bdf7", - "sha256:2e418f0a59473dac424f888dd57e85f77502a593b207809211c76e5396ae4f5c", - "sha256:30c84e3a62cfcb9e3066f25226e131451312a044f1fe2040e69ce792cb7de418", - "sha256:4650d94bb9c947151737ee022b934b7d9a845a7c76e476f3e460f09a0c8c6f39", - "sha256:4dd830a11e8724c9c9379feed1d1be43113f8bcce55f47ea7186d3946769ce26", - "sha256:4f2a2b279efde194877aff1f76cf61c68e840db242a5c7169f1ff0fd59a2b1e2", - "sha256:62d22566b3e3428dfc9ec972014c38ed9a4db4f8969c78f5414012ccd80a149e", - "sha256:669795516d62f38845c7033679c648903200980d68935baaa17ac5c7ae03ae0c", - "sha256:75fcd60d682db3e1f8fbe2b8b0c6761937ad56d01c1dc73edf4ef2748d5b6bc4", - "sha256:9395b0a41e8b7e9a284e3be7060db9d14ad80273841c952c83a5afc241d2bd98", - "sha256:9e37c35fc4e9410093b04a77d11a34c64bf658565e30df7cbe882056088a91c1", - "sha256:a0678793096205a4d784bd99f32803ba8100f639cf3b932dc63b21621390ea7e", - "sha256:b46554ad4dafb2927f88de5a1d207398c5385edbb5c84d30b3ef187c4a3894d8", - "sha256:c867eeccd934920a800f65c6068acdd6b87e80d45cd8c8beefff783b23cdc462", - "sha256:dd0667f5be56fb1b570154c2c0516a528e02d50da121bbbb2cbb0b6f87f59bc2", - "sha256:de2b1c20494bdf47f0160bd88ed05f5e48ae5dc336b8de7cfade71abcc95c0b9", - "sha256:f1df7b2b7740dd777571c732f98adb5aad5450aee32772f1b39249c8a50386f6", - "sha256:ffca69e29079f7880c5392bf675eb8b4146479d976ae1924d01cd92b04cccbcc" + "sha256:0a7a1dd123aecc9f0076934288ceed7fd9a81ba3919f11a855a7887cbe82a02f", + "sha256:0c0763787133dfeec19904c22c7e358b231c87ba3206b211652f8cbe1241deb6", + "sha256:3d52298d0be333583739f1aec9026f3b09fdfe3ddf7c7028cb16d9d2af1cca7e", + "sha256:43bb4b70585f1c2d153e45323a886839f98af8bfa810f7014b20be714c37c447", + "sha256:475963c5b9e116c38ad7347e154e5651d05a2286d86455671f5b1eebba5feb76", + "sha256:64874913367f18eb3013b16123c9fed113962e75d809fca5b78ebfbb73ed93ba", + "sha256:683828e50c339fc9e68720396f2de14253992c495fdddef77a1e17de55f1decc", + "sha256:6ca4000c4a6f95a78c33c7dadbb9495c10880be9c89316aa536eac359ab820ae", + "sha256:75fd817b7061f6378e4659dd792c84c0b60533e867f83e0d1e52d5d8e53df88c", + "sha256:7d81d784bdbed30137aca242ab307f3e65c8d93f4c7b7d8f322110b2e90177f9", + "sha256:8d0af8d3664f142414fd5b15cabfd3b6cc3ef242a3c7a7493257025be5a6955f", + "sha256:9679831005fb16c6df3dd35d17aa31dc0d4d7573d84f0b44cc481490a65c7725", + "sha256:a8f67ebfae9f575d85fa859b54d3bdecaeece74e3274b0b5c5f804d7ca789fe1", + "sha256:acbf5c52db4adb366c064d0b7c7899e3e778d89db585feadd23b06b587d64761", + "sha256:ada4805ed51f5bcaa3a06d3dd94939351869c095e30a2b54264f5a5004b52170", + "sha256:c7354e8f0eca5c110b7e978034cd86ed98a7a5ffcf69ca97535445a595e07b8e", + "sha256:e2e9d8c87120ba2c591f60e32736b82b67f72c37ba88a4c23c81b5b8fa49c018", + "sha256:e467c57121fe1b78a8f68dd9255fbb3bb3f4f7547c6b9e109f31d14569f490c3", + "sha256:ede47b98de79565fcd7f2decb475e2dcc85ee4097743e551fe26cfc7eb3ff143", + "sha256:f58913e9227400f1395c7b800503ebfdb0772f1c33ff8cb4d6451c06cabdf316", + "sha256:fe39f5fd4103ec4ca3cb8600b19216cd1ff316b4990f4c0b6057ad982c0a34d5" ], "index": "pypi", - "version": "==1.17.3" + "version": "==1.17.4" }, "protobuf": { "hashes": [ @@ -66,35 +66,47 @@ }, "scipy": { "hashes": [ - "sha256:0baa64bf42592032f6f6445a07144e355ca876b177f47ad8d0612901c9375bef", - "sha256:243b04730d7223d2b844bda9500310eecc9eda0cba9ceaf0cde1839f8287dfa8", - "sha256:2643cfb46d97b7797d1dbdb6f3c23fe3402904e3c90e6facfe6a9b98d808c1b5", - "sha256:396eb4cdad421f846a1498299474f0a3752921229388f91f60dc3eda55a00488", - "sha256:3ae3692616975d3c10aca6d574d6b4ff95568768d4525f76222fb60f142075b9", - "sha256:435d19f80b4dcf67dc090cc04fde2c5c8a70b3372e64f6a9c58c5b806abfa5a8", - "sha256:46a5e55850cfe02332998b3aef481d33f1efee1960fe6cfee0202c7dd6fc21ab", - "sha256:75b513c462e58eeca82b22fc00f0d1875a37b12913eee9d979233349fce5c8b2", - "sha256:7ccfa44a08226825126c4ef0027aa46a38c928a10f0a8a8483c80dd9f9a0ad44", - "sha256:89dd6a6d329e3f693d1204d5562dd63af0fd7a17854ced17f9cbc37d5b853c8d", - "sha256:a81da2fe32f4eab8b60d56ad43e44d93d392da228a77e229e59b51508a00299c", - "sha256:a9d606d11eb2eec7ef893eb825017fbb6eef1e1d0b98a5b7fc11446ebeb2b9b1", - "sha256:ac37eb652248e2d7cbbfd89619dce5ecfd27d657e714ed049d82f19b162e8d45", - "sha256:cbc0611699e420774e945f6a4e2830f7ca2b3ee3483fca1aa659100049487dd5", - "sha256:d02d813ec9958ed63b390ded463163685af6025cb2e9a226ec2c477df90c6957", - "sha256:dd3b52e00f93fd1c86f2d78243dfb0d02743c94dd1d34ffea10055438e63b99d" + "sha256:0359576d8cc058bd615999cf985e2423dc6cc824666d60e8b8d4810569a04655", + "sha256:07673b5b96dbe28c88f3a53ca9af67f802aa853de7402e31f473b4dd6501c799", + "sha256:0f81e71149539ac09053a3f9165659367b060eceef3bbde11e6600e1c341f1f2", + "sha256:125aa82f7b3d4bd7f77fed6c3c6e31be47e33f129d829799569389ae59f913e7", + "sha256:2dc26e5b3eb86b7adad506b6b04020f6a87e1102c9acd039e937d28bdcee7fa6", + "sha256:2e4b5fdb635dd425bf46fbd6381612692d3c795f1eb6fe62410305a440691d46", + "sha256:33ac3213ee617bbc0eac84d02b130d69093ed7738afb281dfdeb12a9dbdf1530", + "sha256:34c48d922760782732d6f8f4532e320984d1280763c6787c6582021d34c8ad79", + "sha256:3f556f63e070e9596624e42e99d23b259d8f0fc63ec093bef97a9f1c579565b2", + "sha256:470d8fc76ccab6cfff60a9de4ce316a23ee7f63615d948c7446dc7c1bb45042d", + "sha256:4ad7a3ae9831d2085d6f50b81bfcd76368293eafdf31f4ac9f109c6061309c24", + "sha256:61812a7db0d9bc3f13653e52b8ddb1935cf444ec55f39160fc2778aeb2719057", + "sha256:7a0477929e6f9d5928fe81fe75d00b7da9545a49109e66028d85848b18aeef99", + "sha256:9c3221039da50f3b60da70b65d6b020ea26cefbb097116cfec696010432d1f6c", + "sha256:a03939b431994289f39373c57bbe452974a7da724ae7f9620a1beee575434da4", + "sha256:df4dbd3d40db3f667e0145dba5f50954bf28b2dd5b8b400c79d5e3fe8cb67ce2", + "sha256:e837c8068bd1929a533e9d51562faf6584ddb5303d9e218d8c11aa4719dcd617", + "sha256:ecfd45ca0ce1d6c13bef17794b4052cc9a9574f4be8d44c9bcfd7e34294bd2d7", + "sha256:ee5888c62cd83c9bf9927ffcee08434e7d5c81a8f31e5b85af5470e511022c08", + "sha256:f018892621b787b9abf76d51d1f0c21611c71752ebb1891ccf7992e0bf973708", + "sha256:f2d5db81d90d14a32d4aff920f52fca5639bcaaaf87b4f61bce83a1d238f49fc" ], "index": "pypi", - "version": "==1.3.1" + "version": "==1.3.2" }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", + "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" ], - "version": "==1.12.0" + "version": "==1.13.0" } }, "develop": { + "alabaster": { + "hashes": [ + "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", + "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" + ], + "version": "==0.7.12" + }, "atomicwrites": { "hashes": [ "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", @@ -109,6 +121,34 @@ ], "version": "==19.3.0" }, + "babel": { + "hashes": [ + "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", + "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" + ], + "version": "==2.7.0" + }, + "bleach": { + "hashes": [ + "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", + "sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa" + ], + "version": "==3.1.0" + }, + "certifi": { + "hashes": [ + "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", + "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" + ], + "version": "==2019.9.11" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, "coverage": { "hashes": [ "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", @@ -147,6 +187,49 @@ "index": "pypi", "version": "==4.5.4" }, + "decorator": { + "hashes": [ + "sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", + "sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d" + ], + "version": "==4.4.1" + }, + "defusedxml": { + "hashes": [ + "sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93", + "sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5" + ], + "version": "==0.6.0" + }, + "docutils": { + "hashes": [ + "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", + "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", + "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" + ], + "version": "==0.15.2" + }, + "entrypoints": { + "hashes": [ + "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", + "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" + ], + "version": "==0.3" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "imagesize": { + "hashes": [ + "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", + "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" + ], + "version": "==1.1.0" + }, "importlib-metadata": { "hashes": [ "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", @@ -155,6 +238,74 @@ "markers": "python_version < '3.8'", "version": "==0.23" }, + "ipython-genutils": { + "hashes": [ + "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", + "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" + ], + "version": "==0.2.0" + }, + "jinja2": { + "hashes": [ + "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", + "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" + ], + "version": "==2.10.3" + }, + "jsonschema": { + "hashes": [ + "sha256:2fa0684276b6333ff3c0b1b27081f4b2305f0a36cf702a23db50edb141893c3f", + "sha256:94c0a13b4a0616458b42529091624e66700a17f847453e52279e35509a5b7631" + ], + "version": "==3.1.1" + }, + "jupyter-core": { + "hashes": [ + "sha256:464769f7387d7a62a2403d067f1ddc616655b7f77f5d810c0dd62cb54bfd0fb9", + "sha256:a183e0ec2e8f6adddf62b0a3fc6a2237e3e0056d381e536d3e7c7ecc3067e244" + ], + "version": "==4.6.1" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + ], + "version": "==1.1.1" + }, + "mistune": { + "hashes": [ + "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e", + "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4" + ], + "version": "==0.8.4" + }, "more-itertools": { "hashes": [ "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", @@ -190,6 +341,28 @@ ], "version": "==0.4.3" }, + "nbconvert": { + "hashes": [ + "sha256:21fb48e700b43e82ba0e3142421a659d7739b65568cc832a13976a77be16b523", + "sha256:f0d6ec03875f96df45aa13e21fd9b8450c42d7e1830418cccc008c0df725fcee" + ], + "version": "==5.6.1" + }, + "nbformat": { + "hashes": [ + "sha256:b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b", + "sha256:f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402" + ], + "version": "==4.4.0" + }, + "nbsphinx": { + "hashes": [ + "sha256:577a9f26c39fbe0d8eda18940b13661a9b7b4b12c7127bbecdc7edf72b3c4426", + "sha256:c39b8b4d7f3c7b02ba608be3508414c033906d40caa8b6fcfef81c9c8527c47a" + ], + "index": "pypi", + "version": "==0.4.3" + }, "packaging": { "hashes": [ "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", @@ -197,6 +370,12 @@ ], "version": "==19.2" }, + "pandocfilters": { + "hashes": [ + "sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9" + ], + "version": "==1.4.2" + }, "pluggy": { "hashes": [ "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", @@ -211,13 +390,26 @@ ], "version": "==1.8.0" }, - "pyparsing": { + "pygments": { "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", + "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" ], "version": "==2.4.2" }, + "pyparsing": { + "hashes": [ + "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", + "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" + ], + "version": "==2.4.5" + }, + "pyrsistent": { + "hashes": [ + "sha256:eb6545dbeb1aa69ab1fb4809bfbf5a8705e44d92ef8fc7c2361682a47c46c778" + ], + "version": "==0.15.5" + }, "pytest": { "hashes": [ "sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6", @@ -226,12 +418,97 @@ "index": "pypi", "version": "==5.2.2" }, + "pytz": { + "hashes": [ + "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", + "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" + ], + "version": "==2019.3" + }, + "requests": { + "hashes": [ + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + ], + "version": "==2.22.0" + }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", + "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + ], + "version": "==1.13.0" + }, + "snowballstemmer": { + "hashes": [ + "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", + "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" + ], + "version": "==2.0.0" + }, + "sphinx": { + "hashes": [ + "sha256:31088dfb95359384b1005619827eaee3056243798c62724fd3fa4b84ee4d71bd", + "sha256:52286a0b9d7caa31efee301ec4300dbdab23c3b05da1c9024b4e84896fb73d79" + ], + "index": "pypi", + "version": "==2.2.1" + }, + "sphinxcontrib-applehelp": { + "hashes": [ + "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", + "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d" + ], + "version": "==1.0.1" + }, + "sphinxcontrib-devhelp": { + "hashes": [ + "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", + "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981" + ], + "version": "==1.0.1" + }, + "sphinxcontrib-htmlhelp": { + "hashes": [ + "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", + "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7" ], - "version": "==1.12.0" + "version": "==1.0.2" + }, + "sphinxcontrib-jsmath": { + "hashes": [ + "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", + "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" + ], + "version": "==1.0.1" + }, + "sphinxcontrib-qthelp": { + "hashes": [ + "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", + "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f" + ], + "version": "==1.0.2" + }, + "sphinxcontrib-serializinghtml": { + "hashes": [ + "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", + "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768" + ], + "version": "==1.1.3" + }, + "testpath": { + "hashes": [ + "sha256:60e0a3261c149755f4399a1fff7d37523179a70fdc3abdf78de9fc2604aeec7e", + "sha256:bfcf9411ef4bf3db7579063e0546938b1edda3d69f4e1fb8756991f5951f85d4" + ], + "version": "==0.4.4" + }, + "traitlets": { + "hashes": [ + "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44", + "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7" + ], + "version": "==4.3.3" }, "typed-ast": { "hashes": [ @@ -260,11 +537,18 @@ }, "typing-extensions": { "hashes": [ - "sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", - "sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", - "sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed" + "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", + "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", + "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575" + ], + "version": "==3.7.4.1" + }, + "urllib3": { + "hashes": [ + "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", + "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" ], - "version": "==3.7.4" + "version": "==1.25.7" }, "wcwidth": { "hashes": [ @@ -273,6 +557,13 @@ ], "version": "==0.1.7" }, + "webencodings": { + "hashes": [ + "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", + "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + ], + "version": "==0.5.1" + }, "zipp": { "hashes": [ "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..687041a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,53 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'Voicechat Modem (DSP Component)' +copyright = '2019, rlee287' +author = 'rlee287' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "nbsphinx","sphinx.ext.mathjax" +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', "**.ipynb_checkpoints", 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..06a8841 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,20 @@ +.. Voicechat Modem (DSP Component) documentation master file, created by + sphinx-quickstart on Tue Nov 12 21:38:15 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Voicechat Modem (DSP Component)'s documentation! +=========================================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..922152e --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd From 4fc78aee34e8ced2d641b49c0d4e5558e4631c6f Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 15 Nov 2019 19:54:09 -0500 Subject: [PATCH 035/228] Adjust wording of trapezoidal average docstring --- voicechat_modem_dsp/modulators/modulator_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index be9f5d6..3e351b6 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -29,13 +29,15 @@ def previous_resample_interpolate(timeseq, baud, data): Computes the average of the data over the specified interval with integrals. The bounds on the given interval need not be integers. -Linear interpolation is used the average integral formula. +Linear interpolation is used for noninteger bounds. + +See "Trapezoidal Averaging.ipynb" for derivations of these formulas. Note: Due to trapezoidal approximation this will not produce the normal average if the bounds are integers. Both endpoints are explicitly included, unlike normal array slicing. In addition, the values at the extremities receive half the weight -as the rest of the data, following the trapezoidal integraion formula. +as the rest of the data, following the trapezoidal integration formula. """ def average_interval_data(data, begin, end): if end Date: Fri, 15 Nov 2019 19:54:20 -0500 Subject: [PATCH 036/228] Set up Sphinx and Trapezoidal Averaging notebook --- Pipfile | 2 + Pipfile.lock | 177 +++- docs/index.rst | 3 +- docs/notebooks/Trapezoidal Averaging.ipynb | 903 +++++++++++++++++++++ 4 files changed, 1083 insertions(+), 2 deletions(-) create mode 100644 docs/notebooks/Trapezoidal Averaging.ipynb diff --git a/Pipfile b/Pipfile index d249014..bc49a7b 100644 --- a/Pipfile +++ b/Pipfile @@ -7,12 +7,14 @@ name = "pypi" numpy = "*" scipy = "*" protobuf = "*" +ipykernel = "*" [dev-packages] pytest = "*" coverage = "*" sphinx = "*" nbsphinx = "*" +itikz = "*" [dev-packages.mypy] version = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 7822ca1..489b54c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "07e26997d877719eb5260008b7507fe2bc16edc71272cceb6f8e60652a4d6a30" + "sha256": "b469ccdf5c9302c68270bfa14668b4b645725ed69b075762890626b088a77fd1" }, "pipfile-spec": 6, "requires": {}, @@ -14,6 +14,63 @@ ] }, "default": { + "backcall": { + "hashes": [ + "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", + "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2" + ], + "version": "==0.1.0" + }, + "decorator": { + "hashes": [ + "sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", + "sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d" + ], + "version": "==4.4.1" + }, + "ipykernel": { + "hashes": [ + "sha256:1a7def9c986f1ee018c1138d16951932d4c9d4da01dad45f9d34e9899565a22f", + "sha256:b368ad13edb71fa2db367a01e755a925d7f75ed5e09fbd3f06c85e7a8ef108a8" + ], + "index": "pypi", + "version": "==5.1.3" + }, + "ipython": { + "hashes": [ + "sha256:dfd303b270b7b5232b3d08bd30ec6fd685d8a58cabd54055e3d69d8f029f7280", + "sha256:ed7ebe1cba899c1c3ccad6f7f1c2d2369464cc77dba8eebc65e2043e19cda995" + ], + "version": "==7.9.0" + }, + "ipython-genutils": { + "hashes": [ + "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", + "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" + ], + "version": "==0.2.0" + }, + "jedi": { + "hashes": [ + "sha256:786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", + "sha256:ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e" + ], + "version": "==0.15.1" + }, + "jupyter-client": { + "hashes": [ + "sha256:60e6faec1031d63df57f1cc671ed673dced0ed420f4377ea33db37b1c188b910", + "sha256:d0c077c9aaa4432ad485e7733e4d91e48f87b4f4bab7d283d42bb24cbbba0a0f" + ], + "version": "==5.3.4" + }, + "jupyter-core": { + "hashes": [ + "sha256:464769f7387d7a62a2403d067f1ddc616655b7f77f5d810c0dd62cb54bfd0fb9", + "sha256:a183e0ec2e8f6adddf62b0a3fc6a2237e3e0056d381e536d3e7c7ecc3067e244" + ], + "version": "==4.6.1" + }, "numpy": { "hashes": [ "sha256:0a7a1dd123aecc9f0076934288ceed7fd9a81ba3919f11a855a7887cbe82a02f", @@ -41,6 +98,36 @@ "index": "pypi", "version": "==1.17.4" }, + "parso": { + "hashes": [ + "sha256:63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", + "sha256:666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c" + ], + "version": "==0.5.1" + }, + "pexpect": { + "hashes": [ + "sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", + "sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb" + ], + "markers": "sys_platform != 'win32'", + "version": "==4.7.0" + }, + "pickleshare": { + "hashes": [ + "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", + "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" + ], + "version": "==0.7.5" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4", + "sha256:e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31", + "sha256:f15af68f66e664eaa559d4ac8a928111eebd5feda0c11738b5998045224829db" + ], + "version": "==2.0.10" + }, "protobuf": { "hashes": [ "sha256:125713564d8cfed7610e52444c9769b8dcb0b55e25cc7841f2290ee7bc86636f", @@ -64,6 +151,60 @@ "index": "pypi", "version": "==3.10.0" }, + "ptyprocess": { + "hashes": [ + "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", + "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f" + ], + "version": "==0.6.0" + }, + "pygments": { + "hashes": [ + "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", + "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" + ], + "version": "==2.4.2" + }, + "python-dateutil": { + "hashes": [ + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + ], + "version": "==2.8.1" + }, + "pyzmq": { + "hashes": [ + "sha256:01636e95a88d60118479041c6aaaaf5419c6485b7b1d37c9c4dd424b7b9f1121", + "sha256:021dba0d1436516092c624359e5da51472b11ba8edffa334218912f7e8b65467", + "sha256:0463bd941b6aead494d4035f7eebd70035293dd6caf8425993e85ad41de13fa3", + "sha256:05fd51edd81eed798fccafdd49c936b6c166ffae7b32482e4d6d6a2e196af4e6", + "sha256:1fadc8fbdf3d22753c36d4172169d184ee6654f8d6539e7af25029643363c490", + "sha256:22efa0596cf245a78a99060fe5682c4cd00c58bb7614271129215c889062db80", + "sha256:260c70b7c018905ec3659d0f04db735ac830fe27236e43b9dc0532cf7c9873ef", + "sha256:2762c45e289732d4450406cedca35a9d4d71e449131ba2f491e0bf473e3d2ff2", + "sha256:2fc6cada8dc53521c1189596f1898d45c5f68603194d3a6453d6db4b27f4e12e", + "sha256:343b9710a61f2b167673bea1974e70b5dccfe64b5ed10626798f08c1f7227e72", + "sha256:41bf96d5f554598a0632c3ec28e3026f1d6591a50f580df38eff0b8067efb9e7", + "sha256:51c2579e5daab2d039957713174061a0ba3a2c12235e9a493155287d172c1ccd", + "sha256:856b2cdf7a1e2cbb84928e1e8db0ea4018709b39804103d3a409e5584f553f57", + "sha256:85b869abc894672de9aecdf032158ea8ad01e2f0c3b09ef60e3687fb79418096", + "sha256:9055ed3f443edae7dc80f253fc54257f8455fc3062a7832c60887409e27c9f82", + "sha256:93f44739db69234c013a16990e43db1aa0af3cf5a4b8b377d028ff24515fbeb3", + "sha256:98fa3e75ccb22c0dc99654e3dd9ff693b956861459e8c8e8734dd6247b89eb29", + "sha256:9a22c94d2e93af8bebd4fcf5fa38830f5e3b1ff0d4424e2912b07651eb1bafb4", + "sha256:a7d3f4b4bbb5d7866ae727763268b5c15797cbd7b63ea17f3b0ec1067da8994b", + "sha256:b0117e8b87e29c3a195b10a5c42910b2ad10b139e7fa319d1d6f2e18c50e69b1", + "sha256:b645a49376547b3816433a7e2d2a99135c8e651e50497e7ecac3bd126e4bea16", + "sha256:cf0765822e78cf9e45451647a346d443f66792aba906bc340f4e0ac7870c169c", + "sha256:dc398e1e047efb18bfab7a8989346c6921a847feae2cad69fedf6ca12fb99e2c", + "sha256:dd5995ae2e80044e33b5077fb4bc2b0c1788ac6feaf15a6b87a00c14b4bdd682", + "sha256:e03fe5e07e70f245dc9013a9d48ae8cc4b10c33a1968039c5a3b64b5d01d083d", + "sha256:ea09a306144dff2795e48439883349819bef2c53c0ee62a3c2fae429451843bb", + "sha256:f4e37f33da282c3c319849877e34f97f0a3acec09622ec61b7333205bdd13b52", + "sha256:fa4bad0d1d173dee3e8ef3c3eb6b2bb6c723fc7a661eeecc1ecb2fa99860dd45" + ], + "version": "==18.1.0" + }, "scipy": { "hashes": [ "sha256:0359576d8cc058bd615999cf985e2423dc6cc824666d60e8b8d4810569a04655", @@ -97,6 +238,32 @@ "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" ], "version": "==1.13.0" + }, + "tornado": { + "hashes": [ + "sha256:349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c", + "sha256:398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60", + "sha256:4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281", + "sha256:559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5", + "sha256:abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7", + "sha256:c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9", + "sha256:c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5" + ], + "version": "==6.0.3" + }, + "traitlets": { + "hashes": [ + "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44", + "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7" + ], + "version": "==4.3.3" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "version": "==0.1.7" } }, "develop": { @@ -245,6 +412,14 @@ ], "version": "==0.2.0" }, + "itikz": { + "hashes": [ + "sha256:11e536bb06fd298edd431e58a75e14ee3abdb4bee30352d3301edf8ca0532f50", + "sha256:eea45c5b1ee1c0359a936a2a3a535e8ac19404b08fa2dbb35a3a860e3a1c1e4f" + ], + "index": "pypi", + "version": "==0.1.4" + }, "jinja2": { "hashes": [ "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", diff --git a/docs/index.rst b/docs/index.rst index 06a8841..2f2bf51 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,7 @@ Welcome to Voicechat Modem (DSP Component)'s documentation! :maxdepth: 2 :caption: Contents: - + notebooks/Trapezoidal Averaging Indices and tables ================== @@ -18,3 +18,4 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` + diff --git a/docs/notebooks/Trapezoidal Averaging.ipynb b/docs/notebooks/Trapezoidal Averaging.ipynb new file mode 100644 index 0000000..9709860 --- /dev/null +++ b/docs/notebooks/Trapezoidal Averaging.ipynb @@ -0,0 +1,903 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Derivation of Trapezoidal Averaging Code" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook explains how the averaging function uses linear interpolation and trapezoidal integration. Recall that the average in terms of integration is given by\n", + "\n", + "$$\n", + "\\frac{1}{x_2-x_1} \\int_{x_1}^{x_2} f(t) dt\n", + "$$\n", + "\n", + "and that linear interpolation for a fraction $0 \\le \\alpha \\le 1$ between two values $y_1$ and $y_2$ is given by $(1-\\alpha)y_1+\\alpha y_2$.\n", + "\n", + "We will perform trapezoidal integration with data points $y[n]=f(n)$ for integer $n$ while allowing the bounds $x_1$ and $x_2$ to be any real number.\n", + "\n", + "*Note: there appear to be bugs in the SVG renderer of the Jupyter web interface, so subscripts in labels for the graphs may not match those in the text.*" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext itikz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will denote the $x$ coordinate of the beginning point by $x_1$, and the $x$ coordinate of the end point by $x_2$. We attach the letters $i$ and $f$ to the subscript to denote the integer and fractional components of $x_1$ and $x_2$, defined as follows:\n", + "\n", + "$$\n", + "x_{1i}=\\lfloor x_1 \\rfloor \\\\\n", + "x_{1f}=x_1-\\lfloor x_1 \\rfloor \\\\\n", + "x_{2i}=\\lceil x_2 \\rceil -1 \\\\\n", + "x_{2f}=1-\\left(\\lceil x_2 \\rceil - x_2\\right)\n", + "$$\n", + "\n", + "Note that $(0 \\le x_{1f} < 1)$ and $(0 < x_{2f} \\le 1)$.\n", + "\n", + "There are three possible cases that can occur when performing the integration:\n", + "\n", + " - Case 1: The endpoints are in the same trapezoid\n", + " - Case 2: The endpoints are in adjacent trapezoids\n", + " - Case 3: The endpoints are separated from each other" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Case 1: Endpoints in Same Trapezoid" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%itikz --temp-dir\n", + "\\documentclass[tikz]{standalone}\n", + "\\usetikzlibrary{intersections}\n", + "\\begin{document}\n", + "\\def\\xbegin{2.75}\n", + "\\def\\xend{3.75}\n", + "\\begin{tikzpicture}\n", + " % Axes\n", + " \\draw[thick, ->] (-0.5,0) -- (10,0) coordinate[label = {below:$x$}] (xmax);\n", + " \\draw[thick, ->] (0,-0.5) -- (0,6) coordinate[label = {right:$f(x)$}] (ymax);\n", + " \\foreach \\x in {0,2,...,10}\n", + " \\node (\\x point) at (\\x,0) {\\textbullet};\n", + "\n", + " % Curve itself\n", + " \\draw[blue, thick, name path=curve] plot[smooth] coordinates {(-0.5,1) (3,4) (6,2) (10,5)};\n", + " \n", + " % Trapezoid stuff\n", + " \\coordinate [label=below:$x_1$] (xleft) at (\\xbegin,0) {};\n", + " \\coordinate [label=below:$x_2$] (xright) at (\\xend,0) {};\n", + " \\node (topleft high) at (\\xbegin,6) {};\n", + " \\node (topright high) at (\\xend,6) {};\n", + " \\path [name path=trapezoid left line] (xleft)--(topleft high);\n", + " \\path [name path=trapezoid right line] (xright)--(topright high);\n", + " \\path [name intersections={of=trapezoid left line and curve, by=trapezoidleft}];\n", + " \\path [name intersections={of=trapezoid right line and curve, by=trapezoidright}];\n", + " \\draw (xleft) -- (trapezoidleft) -- (trapezoidright) -- (xright) -- cycle;\n", + "\n", + " % Fractional component arrows\n", + " \\draw (2,0) -- (2,2);\n", + " \\draw [->] (2,1.5) -- (\\xbegin,1.5) node[midway, label=above:$x_{1f}$] {};\n", + " \\draw [->] (2,0.5) -- (\\xend,0.5) node[near end, label=above:$x_{2f}$] {};\n", + "\\end{tikzpicture}\n", + "\\end{document}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since $x_{1i}=x_{2i}$, we only have a single trapezoid with width $x_{2f}-x_{1f}$. The heights are the interpolated values at $x_1$ and $x_2$:\n", + "\n", + "$$\n", + "f(x_1)=(1-x_{1f})y[x_{1i}]+x_{1f}y[x_{1i}+1]\n", + "$$\n", + "\n", + "and likewise\n", + "\n", + "$$\n", + "f(x_2)=(1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]\n", + "$$\n", + "\n", + "The area of the trapezoid is\n", + "\n", + "$$\n", + "\\frac{1}{2}\\left(x_{2f}-x_{1f}\\right)\\left((1-x_{1f})y[x_1]+ \\\\\n", + "x_{1f}y[x_1+1]+(1-x_{2f})y[x_2]+x_{2f}y[x_2+1]\\right)\n", + "$$\n", + "\n", + "so the average is\n", + "\n", + "$$\n", + "\\frac{1}{2\\left(x_{2f}-x_{1f}\\right)} \\left(x_{2f}-x_{1f}\\right)\\left((1-x_{1f})y[x_1]+x_{1f}y[x_1+1]\\\\\n", + "+(1-x_{2f})y[x_2]+x_{2f}y[x_2+1]\\right) \\\\\n", + "=\\frac{1}{2}\\left((1-x_{1f})y[x_1]+x_{1f}y[x_1+1]+(1-x_{2f})y[x_2]+x_{2f}y[x_2+1]\\right)\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Case 2: Endpoints in Adjacent Trapezoids" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%itikz --temp-dir\n", + "\\documentclass[tikz]{standalone}\n", + "\\usetikzlibrary{intersections}\n", + "\\begin{document}\n", + "\\def\\xbegin{2.75}\n", + "\\def\\xmid{4}\n", + "\\def\\xend{5.5}\n", + "\\begin{tikzpicture}\n", + " % Axes\n", + " \\draw[thick, ->] (-0.5,0) -- (10,0) coordinate[label = {below:$x$}] (xmax);\n", + " \\draw[thick, ->] (0,-0.5) -- (0,6) coordinate[label = {right:$f(x)$}] (ymax);\n", + " \\foreach \\x in {0,2,...,10}\n", + " \\node (\\x point) at (\\x,0) {\\textbullet};\n", + "\n", + " % Curve itself\n", + " \\draw[blue, thick, name path=curve] plot[smooth] coordinates {(-0.5,1) (3,4) (6,2) (10,5)};\n", + " \n", + " % Trapezoid stuff\n", + " \\coordinate [label=below:$x_1$] (xleft) at (\\xbegin,0) {};\n", + " \\coordinate [label=below:$x_{2i}$] (xmid) at (\\xmid,0) {};\n", + " \\coordinate [label=below:$x_2$] (xright) at (\\xend,0) {};\n", + " \\node (topleft high) at (\\xbegin,6) {};\n", + " \\node (topmid high) at (\\xmid, 6) {};\n", + " \\node (topright high) at (\\xend,6) {};\n", + " \\path [name path=trapezoid left line] (xleft)--(topleft high);\n", + " \\path [name path=trapezoid mid line] (xmid) -- (topmid high);\n", + " \\path [name path=trapezoid right line] (xright)--(topright high);\n", + " \\path [name intersections={of=trapezoid left line and curve, by=trapezoidleft}];\n", + " \\path [name intersections={of=trapezoid mid line and curve, by=trapezoidmid}];\n", + " \\path [name intersections={of=trapezoid right line and curve, by=trapezoidright}];\n", + " \\draw (xleft) -- (trapezoidleft) -- (trapezoidmid) -- (xmid) -- cycle;\n", + " \\draw (xmid) -- (trapezoidmid) -- (trapezoidright) -- (xright) -- cycle;\n", + "\n", + " % Fractional component arrows\n", + " \\draw (2,0) -- (2,2);\n", + " \\draw (4,0) -- (4,1);\n", + " \\draw [->] (2,1.5) -- (\\xbegin,1.5) node[midway, label=above:$x_{1f}$] {};\n", + " \\draw [->] (4,0.5) -- (\\xend,0.5) node[near end, label=above:$x_{2f}$] {};\n", + "\\end{tikzpicture}\n", + "\\end{document}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since $x_{1i}=x_{2i}$, we have two trapezoids with width $1-x_{1f}+x_{2f}$. The heights are the interpolated values at $x_1$ and $x_2$:\n", + "\n", + "$$\n", + "f(x_1)=(1-x_{1f})y[x_{1i}]+x_{1f}y[x_{1i}+1]\n", + "$$\n", + "\n", + "and likewise\n", + "\n", + "$$\n", + "f(x_2)=(1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]\n", + "$$\n", + "\n", + "The area of the two trapezoids together is\n", + "\n", + "$$\n", + "\\frac{1}{2} \\left(1-x_{1f}\\right) \\left((1-x_{1f})y[x_1]+x_{1f}y[x_{2i}]+y[x_{2i}]\\right) \\\\\n", + "+ \\frac{1}{2} x_{2f} \\left((1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]+y[x_{2i}]\\right)\n", + "$$\n", + "\n", + "so the average is\n", + "\n", + "$$\n", + "\\frac{1}{2\\left(1-x_{1f}+x_{2f}\\right)}\\left( \\left(1-x_{1f}\\right) \\left((1-x_{1f})y[x_1]+x_{1f}y[x_{2i}]+y[x_{2i}]\\right) \\\\\n", + "+ x_{2f} \\left((1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]+y[x_{2i}]\\right) \\right)\n", + "$$\n", + "\n", + "which simplifies to\n", + "\n", + "$$\n", + "\\frac{1}{2\\left(1-x_{1f}+x_{2f}\\right)} \\left( \\left((1-x_{1f})^2y[x_1]+x_{1f}(1-x_{1f})y[x_{2i}]\\right) \\\\\n", + "+\\left(x_{2f}(1-x_{2f})y[x_{2i}]+x_{2f}^2y[x_{2i}+1]\\right)+2 y[x_{2i}]\\right)\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Case 3: Separated Endpoints" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%%itikz --temp-dir\n", + "\\documentclass[tikz]{standalone}\n", + "\\usetikzlibrary{intersections}\n", + "\\begin{document}\n", + "\\def\\xbegin{2.75}\n", + "\\def\\xmidl{4}\n", + "\\def\\xmidr{6}\n", + "\\def\\xend{7}\n", + "\\begin{tikzpicture}\n", + " % Axes\n", + " \\draw[thick, ->] (-0.5,0) -- (10,0) coordinate[label = {below:$x$}] (xmax);\n", + " \\draw[thick, ->] (0,-0.5) -- (0,6) coordinate[label = {right:$f(x)$}] (ymax);\n", + " \\foreach \\x in {0,2,...,10}\n", + " \\node (\\x point) at (\\x,0) {\\textbullet};\n", + "\n", + " % Curve itself\n", + " \\draw[blue, thick, name path=curve] plot[smooth] coordinates {(-0.5,1) (3,4) (6,2) (10,5)};\n", + " \n", + " % Trapezoid stuff\n", + " \\coordinate [label=below:$x_1$] (xleft) at (\\xbegin,0) {};\n", + " \\coordinate [label=below:$x_i$] (xmidl) at (\\xmidl,0) {};\n", + " \\coordinate [label=below:$x_{2i}$] (xmidr) at (\\xmidr,0) {};\n", + " \\coordinate [label=below:$x_2$] (xright) at (\\xend,0) {};\n", + " \\node (topleft high) at (\\xbegin,6) {};\n", + " \\node (topmidl high) at (\\xmidl, 6) {};\n", + " \\node (topmidr high) at (\\xmidr, 6) {};\n", + " \\node (topright high) at (\\xend,6) {};\n", + " \\path [name path=trapezoid left line] (xleft)--(topleft high);\n", + " \\path [name path=trapezoid midl line] (xmidl) -- (topmidl high);\n", + " \\path [name path=trapezoid midr line] (xmidr) -- (topmidr high);\n", + " \\path [name path=trapezoid right line] (xright)--(topright high);\n", + " \\path [name intersections={of=trapezoid left line and curve, by=trapezoidleft}];\n", + " \\path [name intersections={of=trapezoid midl line and curve, by=trapezoidmidl}];\n", + " \\path [name intersections={of=trapezoid midr line and curve, by=trapezoidmidr}];\n", + " \\path [name intersections={of=trapezoid right line and curve, by=trapezoidright}];\n", + " \\draw (xleft) -- (trapezoidleft) -- (trapezoidmidl) -- (xmidl) -- cycle;\n", + " \\draw (xmidl) -- (trapezoidmidl) -- (trapezoidmidr) -- (xmidr) -- cycle;\n", + " \\draw (xmidr) -- (trapezoidmidr) -- (trapezoidright) -- (xright) -- cycle;\n", + "\n", + " % Fractional component arrows\n", + " \\draw (2,0) -- (2,2);\n", + " \\draw (6,0) -- (6,1);\n", + " \\draw [->] (2,1.5) -- (\\xbegin,1.5) node[midway, label=above:$x_{1f}$] {};\n", + " \\draw [->] (6,0.5) -- (\\xend,0.5) node[midway, label=above:$x_{2f}$] {};\n", + "\\end{tikzpicture}\n", + "\\end{document}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can apply normal trapezoidal integration to the center trapezoids, leaving the special handling only for the trapezoids at the beginning and end. We previously derived formulas for the end trapezoids, so adding all the trapezoids together produces\n", + "\n", + "$$\n", + "\\frac{1}{2} \\left(1-x_{1f}\\right) \\left((1-x_{1f})y[x_1]+x_{1f}y[x_1+1]+y[x_1+1]\\right) \\\\\n", + "+ \\frac{1}{2} \\left(y[x_1+1]+y[x_1+2]\\right) + \\cdots + \\frac{1}{2} \\left(y[x_{2i}-1]+y[x_{2i}]\\right) \\\\\n", + "\\frac{1}{2} x_{2f} \\left((1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]+y[x_{2i}]\\right)\n", + "$$\n", + "\n", + "The intermediate terms combine in the usual manner, leaving us with\n", + "\n", + "$$\n", + "\\frac{1}{2} \\left(1-x_{1f}\\right) \\left((1-x_{1f})y[x_1]+x_{1f}y[x_1+1]\\right) \\\\\n", + "+ \\sum_{n=x_1+1}^{x_{2i}} y[n] + \\frac{1}{2} x_{2f} \\left((1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]\\right)\n", + "$$\n", + "\n", + "or\n", + "\n", + "$$\n", + "\\frac{1}{2} \\left((1-x_{1f})^2y[x_1]+x_{1f}(1-x_{1f})y[x_{2i}]\\right) \\\\\n", + "+ \\sum_{n=x_1+1}^{x_{2i}} y[n] + \\frac{1}{2} x_{2f} \\left(x_{2f}(1-x_{2f})y[x_{2i}]+x_{2f}^2y[x_{2i}+1]\\right)\n", + "$$\n", + "\n", + "as the total area under the trapezoids. The average is therefore\n", + "\n", + "$$\n", + "\\frac{1}{x_2-x_1} \\left(\n", + "\\frac{1}{2} \\left((1-x_{1f})^2y[x_1]+x_{1f}(1-x_{1f})y[x_{2i}]\\right) \\\\\n", + "+ \\sum_{n=x_1+1}^{x_{2i}} y[n] + \\frac{1}{2} x_{2f} \\left(x_{2f}(1-x_{2f})y[x_{2i}]+x_{2f}^2y[x_{2i}+1]\\right) \\right)\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Final code" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is the final code that combines the three cases to produce an averaging routine using trapezoidal integration.\n", + "\n", + "*This is not synced to the actual code in the repo, but this should theoretically never need to be updated again.*" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "Computes the average of the data over the specified interval with integrals.\n", + "\n", + "The bounds on the given interval need not be integers.\n", + "Linear interpolation is used for noninteger bounds.\n", + "\n", + "Note: Due to trapezoidal approximation this will not produce the\n", + "normal average if the bounds are integers.\n", + "Both endpoints are explicitly included, unlike normal array slicing.\n", + "In addition, the values at the extremities receive half the weight\n", + "as the rest of the data, following the trapezoidal integration formula.\n", + "\"\"\"\n", + "def average_interval_data(data, begin, end):\n", + " if end(len(data)-1):\n", + " raise ValueError(\"Invalid index range specified\")\n", + " width=end-begin\n", + " # Handle special case of width=0\n", + " if width==0:\n", + " index_int=int(np.floor(begin))\n", + " index_frac=begin-np.floor(begin)\n", + " return (1-index_frac)*data[index_int]+index_frac*data[index_int+1]\n", + "\n", + " # Get bounding indicies\n", + " begin_index=int(np.floor(begin))\n", + " begin_frac=begin-begin_index\n", + " end_index=int(np.ceil(end))-1\n", + " end_frac=1-(np.ceil(end)-end)\n", + "\n", + " if end_index-begin_index>0:\n", + " if end_index-begin_index==1:\n", + " # Case 2: two trapezoids with no whole trapezoids in between\n", + " # Compute the portion of the trapzeoid normally folded into sum\n", + " # Lump sum does not work because the widths are smaller\n", + " sum_interval=data[end_index]*(1-begin_frac+end_frac)\n", + " sum_interval*=0.5\n", + " else:\n", + " # Case 3: general trapezoidal integration\n", + " # Increment begin_index to exclude start element\n", + " # Increment end_index to include second-to-last element\n", + " # but exclude last\n", + " sum_interval=sum(data[begin_index+1:end_index+1])\n", + "\n", + " # Compute beginning contribution\n", + " begin_val=(1-begin_frac)**2 * data[begin_index] \\\n", + " + begin_frac*(1-begin_frac)*data[begin_index+1]\n", + " begin_val*=0.5\n", + " # Compute ending contribution\n", + " end_val=end_frac*(1-end_frac)*data[end_index] \\\n", + " + end_frac**2*data[end_index+1]\n", + " end_val*=0.5\n", + "\n", + " # Add this to the sum and average by dividing out width\n", + " sum_interval+=(begin_val+end_val)\n", + " else:\n", + " # Case 1: A single trapezoid\n", + " # begin_index and end_index equal here, but separate for readability\n", + " sum_interval=(end_frac-begin_frac)* \\\n", + " ((1-begin_frac)*data[begin_index]+begin_frac*data[begin_index+1]+ \\\n", + " (1-end_frac)*data[end_index]+end_frac*data[end_index+1])\n", + " sum_interval*=0.5\n", + " return sum_interval/width" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 1fa232e36766ced4e962ee08b84c90cdcd92f870 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 17 Nov 2019 12:16:51 -0500 Subject: [PATCH 037/228] Switch docs to "haiku" theme --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 687041a..9dc5987 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -45,7 +45,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = 'haiku' # default alabaster # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, From 3a5e5973425bb0e7e76b3efe62f90201458b381b Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 17 Nov 2019 12:20:48 -0500 Subject: [PATCH 038/228] Update Pipfile.lock --- Pipfile.lock | 66 ++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 489b54c..56aa83c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -174,36 +174,36 @@ }, "pyzmq": { "hashes": [ - "sha256:01636e95a88d60118479041c6aaaaf5419c6485b7b1d37c9c4dd424b7b9f1121", - "sha256:021dba0d1436516092c624359e5da51472b11ba8edffa334218912f7e8b65467", - "sha256:0463bd941b6aead494d4035f7eebd70035293dd6caf8425993e85ad41de13fa3", - "sha256:05fd51edd81eed798fccafdd49c936b6c166ffae7b32482e4d6d6a2e196af4e6", - "sha256:1fadc8fbdf3d22753c36d4172169d184ee6654f8d6539e7af25029643363c490", - "sha256:22efa0596cf245a78a99060fe5682c4cd00c58bb7614271129215c889062db80", - "sha256:260c70b7c018905ec3659d0f04db735ac830fe27236e43b9dc0532cf7c9873ef", - "sha256:2762c45e289732d4450406cedca35a9d4d71e449131ba2f491e0bf473e3d2ff2", - "sha256:2fc6cada8dc53521c1189596f1898d45c5f68603194d3a6453d6db4b27f4e12e", - "sha256:343b9710a61f2b167673bea1974e70b5dccfe64b5ed10626798f08c1f7227e72", - "sha256:41bf96d5f554598a0632c3ec28e3026f1d6591a50f580df38eff0b8067efb9e7", - "sha256:51c2579e5daab2d039957713174061a0ba3a2c12235e9a493155287d172c1ccd", - "sha256:856b2cdf7a1e2cbb84928e1e8db0ea4018709b39804103d3a409e5584f553f57", - "sha256:85b869abc894672de9aecdf032158ea8ad01e2f0c3b09ef60e3687fb79418096", - "sha256:9055ed3f443edae7dc80f253fc54257f8455fc3062a7832c60887409e27c9f82", - "sha256:93f44739db69234c013a16990e43db1aa0af3cf5a4b8b377d028ff24515fbeb3", - "sha256:98fa3e75ccb22c0dc99654e3dd9ff693b956861459e8c8e8734dd6247b89eb29", - "sha256:9a22c94d2e93af8bebd4fcf5fa38830f5e3b1ff0d4424e2912b07651eb1bafb4", - "sha256:a7d3f4b4bbb5d7866ae727763268b5c15797cbd7b63ea17f3b0ec1067da8994b", - "sha256:b0117e8b87e29c3a195b10a5c42910b2ad10b139e7fa319d1d6f2e18c50e69b1", - "sha256:b645a49376547b3816433a7e2d2a99135c8e651e50497e7ecac3bd126e4bea16", - "sha256:cf0765822e78cf9e45451647a346d443f66792aba906bc340f4e0ac7870c169c", - "sha256:dc398e1e047efb18bfab7a8989346c6921a847feae2cad69fedf6ca12fb99e2c", - "sha256:dd5995ae2e80044e33b5077fb4bc2b0c1788ac6feaf15a6b87a00c14b4bdd682", - "sha256:e03fe5e07e70f245dc9013a9d48ae8cc4b10c33a1968039c5a3b64b5d01d083d", - "sha256:ea09a306144dff2795e48439883349819bef2c53c0ee62a3c2fae429451843bb", - "sha256:f4e37f33da282c3c319849877e34f97f0a3acec09622ec61b7333205bdd13b52", - "sha256:fa4bad0d1d173dee3e8ef3c3eb6b2bb6c723fc7a661eeecc1ecb2fa99860dd45" - ], - "version": "==18.1.0" + "sha256:01b588911714a6696283de3904f564c550c9e12e8b4995e173f1011755e01086", + "sha256:0573b9790aa26faff33fba40f25763657271d26f64bffb55a957a3d4165d6098", + "sha256:0fa82b9fc3334478be95a5566f35f23109f763d1669bb762e3871a8fa2a4a037", + "sha256:1e59b7b19396f26e360f41411a5d4603356d18871049cd7790f1a7d18f65fb2c", + "sha256:2a294b4f44201bb21acc2c1a17ff87fbe57b82060b10ddb00ac03e57f3d7fcfa", + "sha256:355b38d7dd6f884b8ee9771f59036bcd178d98539680c4f87e7ceb2c6fd057b6", + "sha256:4b73d20aec63933bbda7957e30add233289d86d92a0bb9feb3f4746376f33527", + "sha256:4ec47f2b50bdb97df58f1697470e5c58c3c5109289a623e30baf293481ff0166", + "sha256:5541dc8cad3a8486d58bbed076cb113b65b5dd6b91eb94fb3e38a3d1d3022f20", + "sha256:6fca7d11310430e751f9832257866a122edf9d7b635305c5d8c51f74a5174d3d", + "sha256:7369656f89878455a5bcd5d56ca961884f5d096268f71c0750fc33d6732a25e5", + "sha256:75d73ee7ca4b289a2a2dfe0e6bd8f854979fc13b3fe4ebc19381be3b04e37a4a", + "sha256:80c928d5adcfa12346b08d31360988d843b54b94154575cccd628f1fe91446bc", + "sha256:83ce18b133dc7e6789f64cb994e7376c5aa6b4aeced993048bf1d7f9a0fe6d3a", + "sha256:8b8498ceee33a7023deb2f3db907ca41d6940321e282297327a9be41e3983792", + "sha256:8c69a6cbfa94da29a34f6b16193e7c15f5d3220cb772d6d17425ff3faa063a6d", + "sha256:8ff946b20d13a99dc5c21cb76f4b8b253eeddf3eceab4218df8825b0c65ab23d", + "sha256:972d723a36ab6a60b7806faa5c18aa3c080b7d046c407e816a1d8673989e2485", + "sha256:a6c9c42bbdba3f9c73aedbb7671815af1943ae8073e532c2b66efb72f39f4165", + "sha256:aa3872f2ebfc5f9692ef8957fe69abe92d905a029c0608e45ebfcd451ad30ab5", + "sha256:cf08435b14684f7f2ca2df32c9df38a79cdc17c20dc461927789216cb43d8363", + "sha256:d30db4566177a6205ed1badb8dbbac3c043e91b12a2db5ef9171b318c5641b75", + "sha256:d5ac84f38575a601ab20c1878818ffe0d09eb51d6cb8511b636da46d0fd8949a", + "sha256:e37f22eb4bfbf69cd462c7000616e03b0cdc1b65f2d99334acad36ea0e4ddf6b", + "sha256:e6549dd80de7b23b637f586217a4280facd14ac01e9410a037a13854a6977299", + "sha256:ed6205ca0de035f252baa0fd26fdd2bc8a8f633f92f89ca866fd423ff26c6f25", + "sha256:efdde21febb9b5d7a8e0b87ea2549d7e00fda1936459cfb27fb6fca0c36af6c1", + "sha256:f4e72646bfe79ff3adbf1314906bbd2d67ef9ccc71a3a98b8b2ccbcca0ab7bec" + ], + "version": "==18.1.1" }, "scipy": { "hashes": [ @@ -587,11 +587,11 @@ }, "pytest": { "hashes": [ - "sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6", - "sha256:58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4" + "sha256:8e256fe71eb74e14a4d20a5987bb5e1488f0511ee800680aaedc62b9358714e8", + "sha256:ff0090819f669aaa0284d0f4aad1a6d9d67a6efdc6dd4eb4ac56b704f890a0d6" ], "index": "pypi", - "version": "==5.2.2" + "version": "==5.2.4" }, "pytz": { "hashes": [ From 0dd16e21773361588e087426b80d02c40bc51479 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 17 Nov 2019 12:34:03 -0500 Subject: [PATCH 039/228] Drop support for Python 3.4 Weird pipenv interactions when locking dev packages Also prepare to merge type stubs back into actual code --- .travis.yml | 2 -- README.md | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c6669bd..d3bb983 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,6 @@ addons: python: - "3.8" - "3.7" - - "3.4" - "pypy3" - "3.6" - "3.5" @@ -20,7 +19,6 @@ python: cache: pip install: - - if [ $TRAVIS_PYTHON_VERSION = "3.4" ]; then pip install pip==18.0; fi - pip install pipenv - rm Pipfile.lock # Re-lock dependencies - pipenv lock -r > requirements.txt diff --git a/README.md b/README.md index db75833..6d0c93b 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,7 @@ `voicechat-modem` is an implementation of an audio modem communication program that transmits data over a voice/audio channel. The goal is to allow for data transmission and normal speaking over a voice channel simultaneously such that human communication will not interfere with the data transmission. This is the DSP portion of `voicechat-modem` which modulates and demodulates data. It will write audio to an audio file, and eventually, use a protobuf communication channel to communicate with other programs to allow for live data transmission and receiving. + +Requirements: + - Python>=3.5 + - Requirements listed in `Pipfile` From ab612e38125c24c8c472b8fdc4f4417e799c8459 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 24 Nov 2019 14:45:46 -0500 Subject: [PATCH 040/228] Normalize the computed gaussian window --- voicechat_modem_dsp/modulators/modulator_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 3e351b6..1af9502 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -100,7 +100,9 @@ def compute_gaussian_window(fs, sigma_dt): sample_count=np.ceil(6*sigma+1) if sample_count%2==0: sample_count+=1 - return signal.windows.gaussian(sample_count, sigma) + raw_window=signal.windows.gaussian(sample_count, sigma) + raw_window_sum=np.sum(raw_window) + return raw_window/raw_window_sum """ From 9fd1c2cab766a31e0164c8f7e25fedac4093f2c0 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 24 Nov 2019 16:13:26 -0500 Subject: [PATCH 041/228] Create basic ASK modulator code Fine-tune parameters later --- .../modulators/modulator_ask.py | 18 ++++++++++++++---- .../modulators/modulator_ask.pyi | 4 ++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 99dd9b8..cc1ade5 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -15,18 +15,28 @@ def __init__(self, fs, carrier, amp_list, baud): raise ValueError("Invalid amplitudes given") self.fs=fs self.amp_list=dict(enumerate(amp_list)) + self.carrier_freq=carrier self.baud=baud def modulate(self, data): samples_symbol=modulator_utils.samples_per_symbol(self.fs, self.baud) + #sigma_t = w/4k, explain later + gaussian_sigma=(1/self.baud)/(4*2.5) + print(gaussian_sigma) + gaussian_window=modulator_utils.compute_gaussian_window(self.fs,gaussian_sigma) + + amplitude_data=[0]+[self.amp_list[datum] for datum in data]+[0] + interp_sample_count=np.ceil(len(amplitude_data)*samples_symbol) + time_array=modulator_utils.generate_timearray( + self.fs,interp_sample_count) - amplitude_data=[0]+[amp_list[datum] for datum in data]+[0] - time_array=Modulator.generate_time_array( - self.fs,len(amplitude_data)*samples_symbol) interpolated_amplitude=modulator_utils.previous_resample_interpolate( time_array, self.baud, amplitude_data) - + shaped_amplitude=np.convolve(interpolated_amplitude,gaussian_window, + "same") + return (time_array,shaped_amplitude * \ + np.sin(2*np.pi*self.carrier_freq*time_array)) def demodulate(self, time_array, datastream): raise NotImplementedError \ No newline at end of file diff --git a/voicechat_modem_dsp/modulators/modulator_ask.pyi b/voicechat_modem_dsp/modulators/modulator_ask.pyi index 5700e15..cac2f47 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.pyi +++ b/voicechat_modem_dsp/modulators/modulator_ask.pyi @@ -1,7 +1,7 @@ # Stubs for modulators.modulator_ask (Python 3) from .modulator_base import Modulator -from typing import Any, List, Dict +from typing import Any, List, Dict, Tuple class ASKModulator(Modulator): fs: float = ... @@ -9,5 +9,5 @@ class ASKModulator(Modulator): baud: float = ... def __init__(self, fs: float, carrier: float, amp_list: List[float], baud: float) -> None: ... - def modulate(self, data: Any) -> None: ... + def modulate(self, data: Any) -> Tuple[Any, Any]: ... def demodulate(self, time_array: Any, datastream: Any) -> None: ... From 91c30e255b150f230e86d58c90e5657c3c7ff579 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 24 Nov 2019 16:17:39 -0500 Subject: [PATCH 042/228] Remove print statement from ASK Gaussian --- voicechat_modem_dsp/modulators/modulator_ask.py | 1 - 1 file changed, 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index cc1ade5..717ccae 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -22,7 +22,6 @@ def modulate(self, data): samples_symbol=modulator_utils.samples_per_symbol(self.fs, self.baud) #sigma_t = w/4k, explain later gaussian_sigma=(1/self.baud)/(4*2.5) - print(gaussian_sigma) gaussian_window=modulator_utils.compute_gaussian_window(self.fs,gaussian_sigma) amplitude_data=[0]+[self.amp_list[datum] for datum in data]+[0] From cb6f737f867b211d1b8633d5edbd24f5ce14c0cb Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 24 Nov 2019 21:13:18 -0500 Subject: [PATCH 043/228] Remove unused padding functions from utils --- voicechat_modem_dsp/encoders/encode_pad.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/voicechat_modem_dsp/encoders/encode_pad.py b/voicechat_modem_dsp/encoders/encode_pad.py index 7ced4d3..fc5eb43 100644 --- a/voicechat_modem_dsp/encoders/encode_pad.py +++ b/voicechat_modem_dsp/encoders/encode_pad.py @@ -176,21 +176,6 @@ def base_256_decode(datastream): except ValueError: raise ValueError("Illegal symbol detected in datastream") -# Deliberately use NaN here for NumPy propagation later -def make_pad_array(datastream, pad_len): - list_ret=[float("nan")]*pad_len - list_ret+=list(datastream) - list_ret+=[float("nan")]+pad_len - return list_ret - -def unpad_array(datastream): - list_ret=datastream.copy() - while list_ret[0]==float("nan"): - del list_ret[0] - while list_ret[-1]==float("nan"): - del list_ret[-1] - return list_ret - # Convenience mapping to allow for lookup based on len(modulation_list) encode_function_mappings = {2:base_2_encode, 4:base_4_encode, 8:base_8_encode, 16:base_16_encode, From 35df0b53eb076cc4d9fbd72ea325521319be1d6b Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 24 Nov 2019 21:13:55 -0500 Subject: [PATCH 044/228] Use bytes instead of bytearray for semantics in base 16 encoder --- voicechat_modem_dsp/encoders/encode_pad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/encoders/encode_pad.py b/voicechat_modem_dsp/encoders/encode_pad.py index fc5eb43..dc48b47 100644 --- a/voicechat_modem_dsp/encoders/encode_pad.py +++ b/voicechat_modem_dsp/encoders/encode_pad.py @@ -114,7 +114,7 @@ def base_16_decode(datastream): # Should only require a single hex digit if c<0 or hex(c)[-2]!="x": raise ValueError("Illegal symbol detected in datastream") - return base64.b16decode(bytearray("".join([hex(c)[-1] for c in datastream]), + return base64.b16decode(bytes("".join([hex(c)[-1] for c in datastream]), "ascii"),casefold=True) def base_32_encode(bitstream): From cfdcc0b5087173f03af89e6ff1067da3bbc3b8e6 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 24 Nov 2019 21:14:23 -0500 Subject: [PATCH 045/228] Use more numpy/scipy functions in modulation for speed --- voicechat_modem_dsp/modulators/modulator_ask.py | 8 +++++--- voicechat_modem_dsp/modulators/modulator_utils.py | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 717ccae..bff8ad1 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -3,6 +3,7 @@ from . import modulator_utils import numpy as np +from scipy import signal class ASKModulator(Modulator): def __init__(self, fs, carrier, amp_list, baud): @@ -23,16 +24,17 @@ def modulate(self, data): #sigma_t = w/4k, explain later gaussian_sigma=(1/self.baud)/(4*2.5) gaussian_window=modulator_utils.compute_gaussian_window(self.fs,gaussian_sigma) + amplitude_data = \ + np.pad([self.amp_list[datum] for datum in data],1,constant_values=0) - amplitude_data=[0]+[self.amp_list[datum] for datum in data]+[0] interp_sample_count=np.ceil(len(amplitude_data)*samples_symbol) time_array=modulator_utils.generate_timearray( self.fs,interp_sample_count) interpolated_amplitude=modulator_utils.previous_resample_interpolate( time_array, self.baud, amplitude_data) - shaped_amplitude=np.convolve(interpolated_amplitude,gaussian_window, - "same") + shaped_amplitude=signal.convolve(interpolated_amplitude,gaussian_window, + "same",method="fft") return (time_array,shaped_amplitude * \ np.sin(2*np.pi*self.carrier_freq*time_array)) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 1af9502..141ecf4 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -1,5 +1,6 @@ import numpy as np from scipy import signal +from scipy.interpolate import interp1d """ Computes a time array given a sampling rate and a sample count @@ -20,10 +21,9 @@ def samples_per_symbol(fs, baud): Maps a sequence (assumed to be sampled at baud rate) to a timesequence """ def previous_resample_interpolate(timeseq, baud, data): - resampled_data=list() - for cumulative_time in timeseq: - resampled_data.append(data[int(np.floor(cumulative_time*baud))]) - return resampled_data + interp_func=interp1d(range(len(data)),data, + kind="previous",copy=False,bounds_error=False,fill_value=0.0) + return interp_func(timeseq*baud) """ Computes the average of the data over the specified interval with integrals. From 461ebc41f6ea3e5092c83c63e02685864e843fd8 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 27 Nov 2019 22:09:11 -0500 Subject: [PATCH 046/228] Smarter sigma calculation and move into separate func --- .../modulators/modulator_ask.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index bff8ad1..4de4199 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -6,6 +6,11 @@ from scipy import signal class ASKModulator(Modulator): + # norm.isf(1/(2^8)) + sigma_mult_t=2.66 + # norm.isf(0.0001) + sigma_mult_f=3.72 + def __init__(self, fs, carrier, amp_list, baud): if baud>=0.5*carrier: raise ValueError("Baud is too high to be modulated "+ @@ -19,13 +24,20 @@ def __init__(self, fs, carrier, amp_list, baud): self.carrier_freq=carrier self.baud=baud + def _calculate_sigma(self): + #sigma_t = w/4k, explain later + gaussian_sigma_t=(1/self.baud)/(4*ASKModulator.sigma_mult_t) + #Ensure dropoff at carrier frequency is -80dB + gaussian_sigma_f=ASKModulator.sigma_mult_f/(2*np.pi*self.carrier_freq) + return min(gaussian_sigma_t,gaussian_sigma_f) + def modulate(self, data): samples_symbol=modulator_utils.samples_per_symbol(self.fs, self.baud) - #sigma_t = w/4k, explain later - gaussian_sigma=(1/self.baud)/(4*2.5) + + gaussian_sigma=self._calculate_sigma() gaussian_window=modulator_utils.compute_gaussian_window(self.fs,gaussian_sigma) - amplitude_data = \ - np.pad([self.amp_list[datum] for datum in data],1,constant_values=0) + amplitude_data = np.pad([self.amp_list[datum] for datum in data], + 1,constant_values=0) interp_sample_count=np.ceil(len(amplitude_data)*samples_symbol) time_array=modulator_utils.generate_timearray( From e1d342e2dafa22e5a3ccb92384d9ffd923e27813 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 28 Nov 2019 16:59:00 -0500 Subject: [PATCH 047/228] Start ASK demodulator Discard idea of nonuniform timestamps for now --- .../modulators/modulator_ask.py | 50 +++++++++++++------ .../modulators/modulator_ask.pyi | 7 ++- .../modulators/modulator_base.py | 4 +- .../modulators/modulator_base.pyi | 4 +- .../modulators/modulator_utils.py | 2 +- .../modulators/modulator_utils.pyi | 4 +- 6 files changed, 48 insertions(+), 23 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 4de4199..bc82fcd 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -6,8 +6,8 @@ from scipy import signal class ASKModulator(Modulator): - # norm.isf(1/(2^8)) - sigma_mult_t=2.66 + # norm.isf(1/(2*2^8)) + sigma_mult_t=2.89 # norm.isf(0.0001) sigma_mult_f=3.72 @@ -15,6 +15,7 @@ def __init__(self, fs, carrier, amp_list, baud): if baud>=0.5*carrier: raise ValueError("Baud is too high to be modulated "+ "using carrier frequency") + # TODO: set tighter bounds because of 2*carrier? if fs<=2*carrier: raise ValueError("Carrier frequency is too high for sampling rate") if any((x>1 or x<0.1 for x in amp_list)): @@ -24,6 +25,7 @@ def __init__(self, fs, carrier, amp_list, baud): self.carrier_freq=carrier self.baud=baud + @property def _calculate_sigma(self): #sigma_t = w/4k, explain later gaussian_sigma_t=(1/self.baud)/(4*ASKModulator.sigma_mult_t) @@ -31,15 +33,17 @@ def _calculate_sigma(self): gaussian_sigma_f=ASKModulator.sigma_mult_f/(2*np.pi*self.carrier_freq) return min(gaussian_sigma_t,gaussian_sigma_f) - def modulate(self, data): - samples_symbol=modulator_utils.samples_per_symbol(self.fs, self.baud) - - gaussian_sigma=self._calculate_sigma() - gaussian_window=modulator_utils.compute_gaussian_window(self.fs,gaussian_sigma) - amplitude_data = np.pad([self.amp_list[datum] for datum in data], - 1,constant_values=0) + @property + def _samples_per_symbol(self): + return modulator_utils.samples_per_symbol(self.fs,self.baud) + + def modulate(self, datastream): + gaussian_sigma=self._calculate_sigma + gaussian_window=modulator_utils.gaussian_window(self.fs,gaussian_sigma) + amplitude_data = np.pad([self.amp_list[datum] for datum in datastream], + 1,mode="constant",constant_values=0) - interp_sample_count=np.ceil(len(amplitude_data)*samples_symbol) + interp_sample_count=np.ceil(len(amplitude_data)*self._samples_per_symbol) time_array=modulator_utils.generate_timearray( self.fs,interp_sample_count) @@ -48,8 +52,26 @@ def modulate(self, data): shaped_amplitude=signal.convolve(interpolated_amplitude,gaussian_window, "same",method="fft") - return (time_array,shaped_amplitude * \ - np.sin(2*np.pi*self.carrier_freq*time_array)) + return shaped_amplitude * \ + np.cos(2*np.pi*self.carrier_freq*time_array) + + def demodulate(self, modulated_data): + # TODO: find an easier robust way to demodulate? + time_array=modulator_utils.generate_timearray( + self.fs,len(modulated_data)) + demod_amplitude=2*modulated_data*np.exp(2*np.pi*1j*self.carrier_freq*time_array) + + # Compute filter boundaries + carrier_refl=min(2*self.carrier_freq,self.fs-2*self.carrier_freq) + filter_lowend=0.5*self.baud + filter_highend=carrier_refl-filter_lowend + if carrier_refl-4000>self.carrier_freq: + filter_highend=carrier_refl-4000 - def demodulate(self, time_array, datastream): - raise NotImplementedError \ No newline at end of file + fir_filt=modulator_utils.lowpass_fir_filter(self.fs, filter_lowend, filter_highend) + filt_delay=(len(fir_filt)-1)//2 + interval_indexing=filt_delay + while interval_indexing+self._samples_per_symbol<=(len(modulated_data)-1): + pass #stuff + filtered_demod_amplitude=signal.lfilter(fir_filt,1,demod_amplitude) + return np.abs(filtered_demod_amplitude) \ No newline at end of file diff --git a/voicechat_modem_dsp/modulators/modulator_ask.pyi b/voicechat_modem_dsp/modulators/modulator_ask.pyi index cac2f47..9b9bc34 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.pyi +++ b/voicechat_modem_dsp/modulators/modulator_ask.pyi @@ -4,10 +4,13 @@ from .modulator_base import Modulator from typing import Any, List, Dict, Tuple class ASKModulator(Modulator): + sigma_mult_t: float = ... + sigma_mult_f: float = ... fs: float = ... amp_list: Dict[int, float] = ... + carrier_freq: float = ... baud: float = ... def __init__(self, fs: float, carrier: float, amp_list: List[float], baud: float) -> None: ... - def modulate(self, data: Any) -> Tuple[Any, Any]: ... - def demodulate(self, time_array: Any, datastream: Any) -> None: ... + def modulate(self, datastream: Any) -> Any: ... + def demodulate(self, modulated_data: Any) -> None: ... diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index 29d2f12..b390959 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -8,8 +8,8 @@ """ class Modulator(ABC): @abstractmethod - def modulate(self, data): + def modulate(self, datastream): raise NotImplementedError @abstractmethod - def demodulate(self, time_array, datastream): + def demodulate(self, modulated_data): raise NotImplementedError diff --git a/voicechat_modem_dsp/modulators/modulator_base.pyi b/voicechat_modem_dsp/modulators/modulator_base.pyi index bc9ab9e..24c8978 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.pyi +++ b/voicechat_modem_dsp/modulators/modulator_base.pyi @@ -6,6 +6,6 @@ from typing import Any class Modulator(ABC, metaclass=abc.ABCMeta): @abstractmethod - def modulate(self, data: Any) -> Any: ... + def modulate(self, datastream: Any) -> Any: ... @abstractmethod - def demodulate(self, time_array: Any, datastream: Any) -> Any: ... + def demodulate(self, modulated_data: Any) -> Any: ... diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 141ecf4..018e6aa 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -95,7 +95,7 @@ def average_interval_data(data, begin, end): """ Computes a gaussian smoothing filter given sampling rate and sigma time """ -def compute_gaussian_window(fs, sigma_dt): +def gaussian_window(fs, sigma_dt): sigma=sigma_dt*fs sample_count=np.ceil(6*sigma+1) if sample_count%2==0: diff --git a/voicechat_modem_dsp/modulators/modulator_utils.pyi b/voicechat_modem_dsp/modulators/modulator_utils.pyi index 84a4307..420263c 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.pyi +++ b/voicechat_modem_dsp/modulators/modulator_utils.pyi @@ -7,8 +7,8 @@ def samples_per_symbol(fs: float, baud: float) -> float: ... def previous_resample_interpolate(timeseq: Any, baud: float, sequence: Any): ... def average_interval_data(data: Any, begin: float, end: float) -> float: ... -def compute_gaussian_window(fs: float, sigma_dt: float): ... +def gaussian_window(fs: float, sigma_dt: float): ... def fred_harris_fir_tap_count(fs: float, transition_width: float, db_attenuation: float) -> int: ... -def lowpass_fir_filter(dt: float, cutoff_low: float, cutoff_high: float, attenuation: float = ...): ... +def lowpass_fir_filter(fs: float, cutoff_low: float, cutoff_high: float, attenuation: float = ...): ... def linearize_fir(filter: Any) -> None: ... From 8acb653ec7edf75a0c367ae09c1b79f93866d561 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 29 Nov 2019 18:25:48 -0500 Subject: [PATCH 048/228] Finish writing demodulator for ASKModulator --- .../modulators/modulator_ask.py | 39 ++++++++++++++++--- .../modulators/modulator_ask.pyi | 2 +- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index bc82fcd..33583ee 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -4,6 +4,7 @@ import numpy as np from scipy import signal +from scipy.cluster.vq import vq class ASKModulator(Modulator): # norm.isf(1/(2*2^8)) @@ -32,7 +33,7 @@ def _calculate_sigma(self): #Ensure dropoff at carrier frequency is -80dB gaussian_sigma_f=ASKModulator.sigma_mult_f/(2*np.pi*self.carrier_freq) return min(gaussian_sigma_t,gaussian_sigma_f) - + @property def _samples_per_symbol(self): return modulator_utils.samples_per_symbol(self.fs,self.baud) @@ -70,8 +71,34 @@ def demodulate(self, modulated_data): fir_filt=modulator_utils.lowpass_fir_filter(self.fs, filter_lowend, filter_highend) filt_delay=(len(fir_filt)-1)//2 - interval_indexing=filt_delay - while interval_indexing+self._samples_per_symbol<=(len(modulated_data)-1): - pass #stuff - filtered_demod_amplitude=signal.lfilter(fir_filt,1,demod_amplitude) - return np.abs(filtered_demod_amplitude) \ No newline at end of file + + filtered_demod_amplitude=np.abs(signal.lfilter(fir_filt,1,demod_amplitude)) + + # Round to account for floating point weirdness + interval_count=int(np.round( + len(modulated_data)/self._samples_per_symbol)) + interval_offset=filt_delay + list_amplitudes=list() + for i in range(interval_count): + transition_width=ASKModulator.sigma_mult_t*self._calculate_sigma + # Convert above time width into sample width + transition_width*=self.fs + + interval_begin=interval_offset+i*self._samples_per_symbol + # Perform min in order to account for floating point weirdness + interval_end=min(interval_begin+self._samples_per_symbol, + len(modulated_data)-1) + + interval_begin+=transition_width + interval_end-=transition_width + list_amplitudes.append(modulator_utils.average_interval_data(filtered_demod_amplitude, interval_begin, interval_end)) + + list_amplitudes=[[amplitude] for amplitude in list_amplitudes] + code_book=[self.amp_list[i] for i in range(len(self.amp_list))] + code_book.insert(0,0.0) + code_book=[[obs] for obs in code_book] + + vector_cluster=vq(list_amplitudes,code_book,False) + # Subtract data points by 1 and remove 0 symbol + datastream=vector_cluster[0]-1 + return datastream[1:-1] \ No newline at end of file diff --git a/voicechat_modem_dsp/modulators/modulator_ask.pyi b/voicechat_modem_dsp/modulators/modulator_ask.pyi index 9b9bc34..dbecf44 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.pyi +++ b/voicechat_modem_dsp/modulators/modulator_ask.pyi @@ -13,4 +13,4 @@ class ASKModulator(Modulator): def __init__(self, fs: float, carrier: float, amp_list: List[float], baud: float) -> None: ... def modulate(self, datastream: Any) -> Any: ... - def demodulate(self, modulated_data: Any) -> None: ... + def demodulate(self, modulated_data: Any) -> Any: ... From f1ad35c1c980a8680386d0c41a6d3fb256e9d266 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 29 Nov 2019 22:10:21 -0500 Subject: [PATCH 049/228] Add comments to ASK modulator --- .../modulators/modulator_ask.py | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 33583ee..b20e8ed 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -34,46 +34,59 @@ def _calculate_sigma(self): gaussian_sigma_f=ASKModulator.sigma_mult_f/(2*np.pi*self.carrier_freq) return min(gaussian_sigma_t,gaussian_sigma_f) + # Expose modulator_utils calculation here as OOP read-only property @property def _samples_per_symbol(self): return modulator_utils.samples_per_symbol(self.fs,self.baud) def modulate(self, datastream): + # Compute gaussian smoothing kernel gaussian_sigma=self._calculate_sigma gaussian_window=modulator_utils.gaussian_window(self.fs,gaussian_sigma) + + # Map datastream to amplitudes and pad with 0 on both ends amplitude_data = np.pad([self.amp_list[datum] for datum in datastream], 1,mode="constant",constant_values=0) + # Upsample amplitude to actual sampling rate interp_sample_count=np.ceil(len(amplitude_data)*self._samples_per_symbol) time_array=modulator_utils.generate_timearray( self.fs,interp_sample_count) - interpolated_amplitude=modulator_utils.previous_resample_interpolate( time_array, self.baud, amplitude_data) + # Smooth amplitudes with Gaussian kernel shaped_amplitude=signal.convolve(interpolated_amplitude,gaussian_window, "same",method="fft") + # Multiply amplitudes by carrier return shaped_amplitude * \ np.cos(2*np.pi*self.carrier_freq*time_array) def demodulate(self, modulated_data): # TODO: find an easier robust way to demodulate? + # Use complex exponential to allow for phase drift time_array=modulator_utils.generate_timearray( self.fs,len(modulated_data)) demod_amplitude=2*modulated_data*np.exp(2*np.pi*1j*self.carrier_freq*time_array) # Compute filter boundaries + # Lowend is half the baud (i.e. the fundamental of the data) + # Highend blocks fundamental and optionally voice + # TODO: improve this part carrier_refl=min(2*self.carrier_freq,self.fs-2*self.carrier_freq) filter_lowend=0.5*self.baud filter_highend=carrier_refl-filter_lowend if carrier_refl-4000>self.carrier_freq: filter_highend=carrier_refl-4000 + # Construct FIR filter, filter demodulated signal, and discard phase fir_filt=modulator_utils.lowpass_fir_filter(self.fs, filter_lowend, filter_highend) filt_delay=(len(fir_filt)-1)//2 filtered_demod_amplitude=np.abs(signal.lfilter(fir_filt,1,demod_amplitude)) + # Extract the original amplitudes via averaging of plateau + # Round to account for floating point weirdness interval_count=int(np.round( len(modulated_data)/self._samples_per_symbol)) @@ -81,7 +94,7 @@ def demodulate(self, modulated_data): list_amplitudes=list() for i in range(interval_count): transition_width=ASKModulator.sigma_mult_t*self._calculate_sigma - # Convert above time width into sample width + # Convert above time width into sample point width transition_width*=self.fs interval_begin=interval_offset+i*self._samples_per_symbol @@ -89,16 +102,22 @@ def demodulate(self, modulated_data): interval_end=min(interval_begin+self._samples_per_symbol, len(modulated_data)-1) + # Shrink interval by previously calculated transition width interval_begin+=transition_width interval_end-=transition_width + # Find the amplitude by averaging list_amplitudes.append(modulator_utils.average_interval_data(filtered_demod_amplitude, interval_begin, interval_end)) + # Convert amplitude observations and mapping into vq arguments + # Insert the null symbol 0 to account for beginning and end list_amplitudes=[[amplitude] for amplitude in list_amplitudes] code_book=[self.amp_list[i] for i in range(len(self.amp_list))] code_book.insert(0,0.0) code_book=[[obs] for obs in code_book] + # Map averages to amplitude points vector_cluster=vq(list_amplitudes,code_book,False) - # Subtract data points by 1 and remove 0 symbol + # Subtract data points by 1 and remove 0 padding + # Neat side effect: -1 is an invalid data point datastream=vector_cluster[0]-1 - return datastream[1:-1] \ No newline at end of file + return datastream[1:-1] From 5bb43b9fd6200013e001fda71f5bf767302367a2 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 29 Nov 2019 22:30:36 -0500 Subject: [PATCH 050/228] Write unit integrity tests for ASK TODO: check robustness for randomized property testing TODO: test sanity checks for parameters --- test/test_modulator_integrity.py | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 test/test_modulator_integrity.py diff --git a/test/test_modulator_integrity.py b/test/test_modulator_integrity.py new file mode 100644 index 0000000..4611445 --- /dev/null +++ b/test/test_modulator_integrity.py @@ -0,0 +1,67 @@ +import random +import numpy as np + +from voicechat_modem_dsp.encoders.encode_pad import * +from voicechat_modem_dsp.modulators.modulator_ask import ASKModulator + +import pytest + +def get_rand_float(lower, upper): + return random.random()*(upper-lower)+lower + +def test_unit_ask_integrity_novoice(): + amplitude_list=list(np.geomspace(0.1,1,16)) + # Shuffle as opposed to complete random to test all 0x00-0xff + list_data=list(range(256)) + bitstream=bytes(list_data) + datastream=base_16_encode(bitstream) + + sampling_freq=48000 + carrier_freq=1000 + baud_rate=250 + + modulator=ASKModulator(sampling_freq,carrier_freq,amplitude_list,baud_rate) + modulated_data=modulator.modulate(datastream) + demodulated_datastream=modulator.demodulate(modulated_data) + recovered_bitstream=base_16_decode(demodulated_datastream) + + assert bitstream==recovered_bitstream + +def test_unit_ask_integrity_voice(): + amplitude_list=list(np.geomspace(0.1,1,16)) + # Shuffle as opposed to complete random to test all 0x00-0xff + list_data=list(range(256)) + bitstream=bytes(list_data) + datastream=base_16_encode(bitstream) + + sampling_freq=48000 + carrier_freq=8000 + baud_rate=1000 + + modulator=ASKModulator(sampling_freq,carrier_freq,amplitude_list,baud_rate) + modulated_data=modulator.modulate(datastream) + demodulated_datastream=modulator.demodulate(modulated_data) + recovered_bitstream=base_16_decode(demodulated_datastream) + + assert bitstream==recovered_bitstream + +@pytest.mark.skip(reason="Need to manually check behavior with pathological inputs") +def test_property_ask_integrity(): + amplitude_list=list(np.geomspace(0.1,1,16)) + for _ in range(4): + # Shuffle as opposed to complete random to test all 0x00-0xff + list_data=list(range(256)) + random.shuffle(list_data) + bitstream=bytes(list_data) + datastream=base_16_encode(bitstream) + + sampling_freq=get_rand_float(8000,48000) + carrier_freq=get_rand_float(100,0.5*sampling_freq) + baud_rate=get_rand_float(50,0.5*carrier_freq) + + modulator=ASKModulator(sampling_freq,carrier_freq,amplitude_list,baud_rate) + modulated_data=modulator.modulate(datastream) + demodulated_datastream=modulator.demodulate(modulated_data) + recovered_bistream=base_16_decode(demodulated_datastream) + + assert bitstream==recovered_bistream \ No newline at end of file From 2a5771f885915358ba42e26d6f1f20d3fedac001 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 29 Nov 2019 22:37:43 -0500 Subject: [PATCH 051/228] Fix test typos and cast time sample count to int --- test/test_modulator_integrity.py | 4 ++-- voicechat_modem_dsp/modulators/modulator_ask.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test_modulator_integrity.py b/test/test_modulator_integrity.py index 4611445..9cae82b 100644 --- a/test/test_modulator_integrity.py +++ b/test/test_modulator_integrity.py @@ -62,6 +62,6 @@ def test_property_ask_integrity(): modulator=ASKModulator(sampling_freq,carrier_freq,amplitude_list,baud_rate) modulated_data=modulator.modulate(datastream) demodulated_datastream=modulator.demodulate(modulated_data) - recovered_bistream=base_16_decode(demodulated_datastream) + recovered_bitstream=base_16_decode(demodulated_datastream) - assert bitstream==recovered_bistream \ No newline at end of file + assert bitstream==recovered_bitstream diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index b20e8ed..4e44baf 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -49,7 +49,8 @@ def modulate(self, datastream): 1,mode="constant",constant_values=0) # Upsample amplitude to actual sampling rate - interp_sample_count=np.ceil(len(amplitude_data)*self._samples_per_symbol) + interp_sample_count=int(np.ceil( + len(amplitude_data)*self._samples_per_symbol)) time_array=modulator_utils.generate_timearray( self.fs,interp_sample_count) interpolated_amplitude=modulator_utils.previous_resample_interpolate( From 7b907cc2e593785b4a41333ccf733e2ea3c5cb82 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 29 Nov 2019 22:47:53 -0500 Subject: [PATCH 052/228] Remove incorrect shuffling comment from ASK unit test --- test/test_modulator_integrity.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_modulator_integrity.py b/test/test_modulator_integrity.py index 9cae82b..3e8fdca 100644 --- a/test/test_modulator_integrity.py +++ b/test/test_modulator_integrity.py @@ -11,7 +11,6 @@ def get_rand_float(lower, upper): def test_unit_ask_integrity_novoice(): amplitude_list=list(np.geomspace(0.1,1,16)) - # Shuffle as opposed to complete random to test all 0x00-0xff list_data=list(range(256)) bitstream=bytes(list_data) datastream=base_16_encode(bitstream) From 48aa9c7e155101b40345cf5ef18d5aa95e3fe620 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 30 Nov 2019 22:13:12 -0500 Subject: [PATCH 053/228] Add property test of constant integration for averager --- test/test_modulator_utils.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index ed3880a..eb1b5d7 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -39,6 +39,22 @@ def test_unit_average_invalid(): with pytest.raises(ValueError, match=r"Invalid index.*"): average_interval_data(dataseq,-1,28.9) +def test_property_average_constant(): + for _ in range(256): + n=random.randint(1,5000) + const_val=random.random() + list_constant=[const_val]*n + for _ in range(16): + lower_bound=random.random()*n + upper_bound=random.random()*n + if upper_bound Date: Sun, 1 Dec 2019 21:49:21 -0500 Subject: [PATCH 054/228] Increase count for test_property_average_constant and add pytest verbose output --- run_tests_coverage.sh | 2 +- test/test_modulator_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/run_tests_coverage.sh b/run_tests_coverage.sh index 6d706e3..08ea4e2 100644 --- a/run_tests_coverage.sh +++ b/run_tests_coverage.sh @@ -1,5 +1,5 @@ #!/bin/sh -coverage run --branch --source 'test,voicechat_modem_dsp' -m pytest +coverage run --branch --source 'test,voicechat_modem_dsp' -m pytest -v test_status=$? coverage report -m coverage xml -i diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index eb1b5d7..3ae0c1b 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -40,7 +40,7 @@ def test_unit_average_invalid(): average_interval_data(dataseq,-1,28.9) def test_property_average_constant(): - for _ in range(256): + for _ in range(1024): n=random.randint(1,5000) const_val=random.random() list_constant=[const_val]*n From 0c8c082d2766cb12ecb710ddc84fa16349b065b3 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 1 Dec 2019 21:51:50 -0500 Subject: [PATCH 055/228] Reduce average test count again Dummy change to re-verify build pipeline --- test/test_modulator_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index 3ae0c1b..eb1b5d7 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -40,7 +40,7 @@ def test_unit_average_invalid(): average_interval_data(dataseq,-1,28.9) def test_property_average_constant(): - for _ in range(1024): + for _ in range(256): n=random.randint(1,5000) const_val=random.random() list_constant=[const_val]*n From c5d860f4de93bc0f76fb68ea85285bb468c488f3 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 2 Dec 2019 15:29:47 -0500 Subject: [PATCH 056/228] Add obsolescence marker to Trapezoidal Averaging notebook --- docs/notebooks/Trapezoidal Averaging.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/notebooks/Trapezoidal Averaging.ipynb b/docs/notebooks/Trapezoidal Averaging.ipynb index 9709860..07f61f1 100644 --- a/docs/notebooks/Trapezoidal Averaging.ipynb +++ b/docs/notebooks/Trapezoidal Averaging.ipynb @@ -11,6 +11,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "**NOTE: This is now obsolete since the new function uses `numpy.trapz`. This notebook will be removed at a later date.**\n", + "\n", "This notebook explains how the averaging function uses linear interpolation and trapezoidal integration. Recall that the average in terms of integration is given by\n", "\n", "$$\n", @@ -900,4 +902,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From 89f95fc62846b55711d9fd2781e07f80267afd1a Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 2 Dec 2019 15:31:11 -0500 Subject: [PATCH 057/228] Fix off-by-one error in modulator_utils constant test --- test/test_modulator_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index eb1b5d7..ecc9de1 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -45,8 +45,8 @@ def test_property_average_constant(): const_val=random.random() list_constant=[const_val]*n for _ in range(16): - lower_bound=random.random()*n - upper_bound=random.random()*n + lower_bound=random.random()*(n-1) + upper_bound=random.random()*(n-1) if upper_bound Date: Mon, 2 Dec 2019 15:31:45 -0500 Subject: [PATCH 058/228] Replace buggy averager with function based on numpy.trapz --- .../modulators/modulator_utils.py | 61 +++++++------------ 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 018e6aa..f7ee46f 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -31,14 +31,13 @@ def previous_resample_interpolate(timeseq, baud, data): The bounds on the given interval need not be integers. Linear interpolation is used for noninteger bounds. -See "Trapezoidal Averaging.ipynb" for derivations of these formulas. - Note: Due to trapezoidal approximation this will not produce the normal average if the bounds are integers. Both endpoints are explicitly included, unlike normal array slicing. In addition, the values at the extremities receive half the weight as the rest of the data, following the trapezoidal integration formula. """ + def average_interval_data(data, begin, end): if end0: - if end_index-begin_index==1: - # Case 2: two trapezoids with no whole trapezoids in between - # Compute the portion of the trapzeoid normally folded into sum - # Lump sum does not work because the widths are smaller - sum_interval=data[end_index]*(1-begin_frac+end_frac) - sum_interval*=0.5 - else: - # Case 3: general trapezoidal integration - # Increment begin_index to exclude start element - # Increment end_index to include second-to-last element - # but exclude last - sum_interval=sum(data[begin_index+1:end_index+1]) - - # Compute beginning contribution - begin_val=(1-begin_frac)**2 * data[begin_index] \ - + begin_frac*(1-begin_frac)*data[begin_index+1] - begin_val*=0.5 - # Compute ending contribution - end_val=end_frac*(1-end_frac)*data[end_index] \ - + end_frac**2*data[end_index+1] - end_val*=0.5 - - # Add this to the sum and average by dividing out width - sum_interval+=(begin_val+end_val) + # Calculate linear interpolation for beginning point + begin_int=int(np.floor(begin)) + begin_frac=begin-np.floor(begin_int) + begin_lininterp=(1-begin_frac)*data[begin_int]+begin_frac*data[begin_int+1] + + # Calculate linear interpolation for end point + # Handle special case where end is actually end to avoid indexing errors + if end==len(data)-1: + end_lininterp=data[end] else: - # Case 1: A single trapezoid - # begin_index and end_index equal here, but separate for readability - sum_interval=(end_frac-begin_frac)* \ - ((1-begin_frac)*data[begin_index]+begin_frac*data[begin_index+1]+ \ - (1-end_frac)*data[end_index]+end_frac*data[end_index+1]) - sum_interval*=0.5 - return sum_interval/width + end_int=int(np.floor(end)) + end_frac=end-np.floor(end) + end_lininterp=(1-end_frac)*data[end_int]+end_frac*data[end_int+1] + + # Construct input to numpy.trapz + x_array=list(range(int(np.ceil(begin)),int(np.floor(end))+1)) + x_array=np.asarray([begin]+x_array+[end]) + + y_array=[data[i] for i in range(int(np.ceil(begin)),int(np.floor(end))+1)] + y_array=np.asarray([begin_lininterp]+y_array+[end_lininterp]) + return np.trapz(y_array,x_array)/width """ Computes a gaussian smoothing filter given sampling rate and sigma time From b502a97cf08e6906ca43db025d27a736810aaefd Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 2 Dec 2019 15:55:50 -0500 Subject: [PATCH 059/228] Handle specific edge case of single element average Literal 1 in 5000 chance that this bug would have been tripped by random tests --- test/test_modulator_utils.py | 4 ++++ voicechat_modem_dsp/modulators/modulator_utils.py | 13 ++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index ecc9de1..a79c5dc 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -32,6 +32,10 @@ def test_unit_average_float(): dataseq=np.asarray([2,2,2,3,4]) assert average_interval_data(dataseq,0.5,1.75)==2 +def test_unit_average_single(): + dataseq=np.asarray([1]) + assert average_interval_data(dataseq,0,0)==1 + def test_unit_average_invalid(): dataseq=[4,1,3,6,1,2,10,2,5] with pytest.raises(ValueError, match=r".*must be larger than.*"): diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index f7ee46f..18026a3 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -48,15 +48,18 @@ def average_interval_data(data, begin, end): if width==0: index_int=int(np.floor(begin)) index_frac=begin-np.floor(begin) - return (1-index_frac)*data[index_int]+index_frac*data[index_int+1] - - # Calculate linear interpolation for beginning point + if index_int==len(data)-1: + return data[index_int] + else: + return (1-index_frac)*data[index_int]+index_frac*data[index_int+1] + + # Calculate linear interpolation for endpoints + # Handle special cases where endpoints are end to avoid indexing errors + # Do not do this for begin as width==0 would have taken care of that already begin_int=int(np.floor(begin)) begin_frac=begin-np.floor(begin_int) begin_lininterp=(1-begin_frac)*data[begin_int]+begin_frac*data[begin_int+1] - # Calculate linear interpolation for end point - # Handle special case where end is actually end to avoid indexing errors if end==len(data)-1: end_lininterp=data[end] else: From d4c3b29f72dc0cfa5cad955e3b9977bd9d49285a Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 9 Dec 2019 14:53:07 -0500 Subject: [PATCH 060/228] Create ModulationIntegrityWarning class for future use --- voicechat_modem_dsp/modulators/modulator_utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 18026a3..4c55c8e 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -2,6 +2,12 @@ from scipy import signal from scipy.interpolate import interp1d +""" +Warning to be raised when modulators cannot guarantee data accuracy +""" +class ModulationIntegrityWarning(UserWarning): + pass + """ Computes a time array given a sampling rate and a sample count """ From 5089d80b4ea4f0c32d15de6271b659533a8c8375 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 9 Dec 2019 14:57:07 -0500 Subject: [PATCH 061/228] Use firls for lowpass filter design --- voicechat_modem_dsp/modulators/modulator_utils.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 4c55c8e..fbd268c 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -111,12 +111,19 @@ def fred_harris_fir_tap_count(fs, transition_width, db_attenuation): """ Computes lowpass FIR filter given cutoffs -Uses the SciPy implementation of the Remez Exchange Algorithm +Uses scipy.signal.firls for Least Squares FIR Filter Design """ def lowpass_fir_filter(fs,cutoff_low,cutoff_high,attenuation=80): + if cutoff_low>=cutoff_high: + raise ValueError("High cutoff must be larger than low cutoff") tap_count=fred_harris_fir_tap_count(fs,cutoff_high-cutoff_low,attenuation) - lowpass_filt=signal.remez(tap_count, - [0,cutoff_low,cutoff_high,0.5*fs],[1,0],fs=fs) + + # Remez would sometimes return NaN arrays as filters + # Least-Squares is not iterative so it may have better stability? + #lowpass_filt=signal.remez(tap_count, + # [0,cutoff_low,cutoff_high,0.5*fs],[1,0],fs=fs) + lowpass_filt=signal.firls(tap_count,[0,cutoff_low,cutoff_high,0.5*fs], + [1,1,0,0],fs=fs) # TODO: remove zeros? return lowpass_filt From 1b6968d6a6b7a2e1b9f3c5fddde6f43877052166 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 10 Dec 2019 09:22:46 -0500 Subject: [PATCH 062/228] Reduce count for modulator utils average property test No longer need because now using numpy.trapz --- test/test_modulator_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index a79c5dc..8e8c4cd 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -44,7 +44,7 @@ def test_unit_average_invalid(): average_interval_data(dataseq,-1,28.9) def test_property_average_constant(): - for _ in range(256): + for _ in range(64): n=random.randint(1,5000) const_val=random.random() list_constant=[const_val]*n @@ -60,7 +60,7 @@ def test_property_average_constant(): assert np.abs(average-const_val) Date: Tue, 10 Dec 2019 09:23:49 -0500 Subject: [PATCH 063/228] Finish fixing ASK Demodulator and add an instance of ModulationIntegrityWarning --- test/test_modulator_integrity.py | 26 +++++++++++------ .../modulators/modulator_ask.py | 28 ++++++++++++++----- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/test/test_modulator_integrity.py b/test/test_modulator_integrity.py index 3e8fdca..380138e 100644 --- a/test/test_modulator_integrity.py +++ b/test/test_modulator_integrity.py @@ -1,8 +1,10 @@ import random +import warnings import numpy as np from voicechat_modem_dsp.encoders.encode_pad import * from voicechat_modem_dsp.modulators.modulator_ask import ASKModulator +from voicechat_modem_dsp.modulators.modulator_utils import ModulationIntegrityWarning import pytest @@ -44,10 +46,10 @@ def test_unit_ask_integrity_voice(): assert bitstream==recovered_bitstream -@pytest.mark.skip(reason="Need to manually check behavior with pathological inputs") def test_property_ask_integrity(): - amplitude_list=list(np.geomspace(0.1,1,16)) - for _ in range(4): + amplitude_list=list(np.linspace(0.1,1,16)) + count_run=0 + while count_run<256: # Shuffle as opposed to complete random to test all 0x00-0xff list_data=list(range(256)) random.shuffle(list_data) @@ -55,12 +57,20 @@ def test_property_ask_integrity(): datastream=base_16_encode(bitstream) sampling_freq=get_rand_float(8000,48000) - carrier_freq=get_rand_float(100,0.5*sampling_freq) - baud_rate=get_rand_float(50,0.5*carrier_freq) + carrier_freq=get_rand_float(256,sampling_freq/3) + baud_rate=get_rand_float(128,carrier_freq/4) + + with warnings.catch_warnings(): + try: + modulator=ASKModulator(sampling_freq, + carrier_freq,amplitude_list,baud_rate) + except ModulationIntegrityWarning: + continue - modulator=ASKModulator(sampling_freq,carrier_freq,amplitude_list,baud_rate) modulated_data=modulator.modulate(datastream) - demodulated_datastream=modulator.demodulate(modulated_data) - recovered_bitstream=base_16_decode(demodulated_datastream) + modulator.demodulate(modulated_data, True) + demodulated_bundle=modulator.demodulate(modulated_data) + recovered_bitstream=base_16_decode(demodulated_bundle) + count_run+=1 assert bitstream==recovered_bitstream diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 4e44baf..70b27af 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -1,11 +1,14 @@ from .modulator_base import Modulator from . import modulator_utils +from .modulator_utils import ModulationIntegrityWarning import numpy as np from scipy import signal from scipy.cluster.vq import vq +import warnings + class ASKModulator(Modulator): # norm.isf(1/(2*2^8)) sigma_mult_t=2.89 @@ -16,8 +19,8 @@ def __init__(self, fs, carrier, amp_list, baud): if baud>=0.5*carrier: raise ValueError("Baud is too high to be modulated "+ "using carrier frequency") - # TODO: set tighter bounds because of 2*carrier? - if fs<=2*carrier: + # Nyquist limit + if carrier>=0.5*fs: raise ValueError("Carrier frequency is too high for sampling rate") if any((x>1 or x<0.1 for x in amp_list)): raise ValueError("Invalid amplitudes given") @@ -25,10 +28,16 @@ def __init__(self, fs, carrier, amp_list, baud): self.amp_list=dict(enumerate(amp_list)) self.carrier_freq=carrier self.baud=baud + # Nyquist aliased 2*carrier=carrier + if carrier>=(1/3)*fs: + warnings.warn("Carrier frequency is too high to guarantee" + "proper lowpass reconstruction", + ModulationIntegrityWarning) + # TODO: additional warnings relating to filter overshoot and the like @property def _calculate_sigma(self): - #sigma_t = w/4k, explain later + #sigma_t = w/4k, at most half of the pulse is smoothed away gaussian_sigma_t=(1/self.baud)/(4*ASKModulator.sigma_mult_t) #Ensure dropoff at carrier frequency is -80dB gaussian_sigma_f=ASKModulator.sigma_mult_f/(2*np.pi*self.carrier_freq) @@ -72,7 +81,7 @@ def demodulate(self, modulated_data): # Compute filter boundaries # Lowend is half the baud (i.e. the fundamental of the data) - # Highend blocks fundamental and optionally voice + # Highend blocks fundamental of data and optionally voice # TODO: improve this part carrier_refl=min(2*self.carrier_freq,self.fs-2*self.carrier_freq) filter_lowend=0.5*self.baud @@ -93,6 +102,7 @@ def demodulate(self, modulated_data): len(modulated_data)/self._samples_per_symbol)) interval_offset=filt_delay list_amplitudes=list() + for i in range(interval_count): transition_width=ASKModulator.sigma_mult_t*self._calculate_sigma # Convert above time width into sample point width @@ -104,11 +114,15 @@ def demodulate(self, modulated_data): len(modulated_data)-1) # Shrink interval by previously calculated transition width - interval_begin+=transition_width - interval_end-=transition_width + # Skip doing so for first and last sample + if i!=0: + interval_begin+=transition_width + if i!=interval_count-1: + interval_end-=transition_width # Find the amplitude by averaging list_amplitudes.append(modulator_utils.average_interval_data(filtered_demod_amplitude, interval_begin, interval_end)) + list_amplitudes_copy=list_amplitudes.copy() # Convert amplitude observations and mapping into vq arguments # Insert the null symbol 0 to account for beginning and end list_amplitudes=[[amplitude] for amplitude in list_amplitudes] @@ -117,7 +131,7 @@ def demodulate(self, modulated_data): code_book=[[obs] for obs in code_book] # Map averages to amplitude points - vector_cluster=vq(list_amplitudes,code_book,False) + vector_cluster=vq(list_amplitudes,code_book) # Subtract data points by 1 and remove 0 padding # Neat side effect: -1 is an invalid data point datastream=vector_cluster[0]-1 From 9406cc1a005c2093a89825af40a44d624ce05f64 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 10 Dec 2019 09:30:03 -0500 Subject: [PATCH 064/228] Forgot to remove debug boolean from call in property test --- test/test_modulator_integrity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_modulator_integrity.py b/test/test_modulator_integrity.py index 380138e..65ed23c 100644 --- a/test/test_modulator_integrity.py +++ b/test/test_modulator_integrity.py @@ -68,7 +68,7 @@ def test_property_ask_integrity(): continue modulated_data=modulator.modulate(datastream) - modulator.demodulate(modulated_data, True) + modulator.demodulate(modulated_data) demodulated_bundle=modulator.demodulate(modulated_data) recovered_bitstream=base_16_decode(demodulated_bundle) count_run+=1 From b27beb8a93390fa60c6d5186219f98a243cd250a Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 10 Dec 2019 17:10:54 -0500 Subject: [PATCH 065/228] Use pytest decorators to mark unit vs property --- pytest.ini | 4 ++++ run_tests_coverage.sh | 14 +++++++++++--- test/test_bitstream.py | 5 +++++ test/test_ecc.py | 4 ++++ test/test_encoders.py | 13 +++++++++++++ test/test_modulator_integrity.py | 3 +++ test/test_modulator_utils.py | 8 ++++++++ 7 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..5ae4ea3 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +markers = + unit: Unit tests using specific input and output + property: Property tests using randomized input diff --git a/run_tests_coverage.sh b/run_tests_coverage.sh index 08ea4e2..0cf8dd5 100644 --- a/run_tests_coverage.sh +++ b/run_tests_coverage.sh @@ -1,6 +1,14 @@ #!/bin/sh -coverage run --branch --source 'test,voicechat_modem_dsp' -m pytest -v -test_status=$? +COVERAGE_FILE='.coverage.unit' coverage run --branch --source 'test,voicechat_modem_dsp' -m pytest -v -m unit +test_status_unit=$? +COVERAGE_FILE='.coverage.property' coverage run --branch --source 'test,voicechat_modem_dsp' -m pytest -v -m property +test_status_property=$? +coverage combine coverage report -m coverage xml -i -exit $test_status +if [ $test_status_unit -eq 0 ] && [ $test_status_property -eq 0 ]; then + exit 0 +else + exit 1 +fi + diff --git a/test/test_bitstream.py b/test/test_bitstream.py index eb887c7..439358f 100644 --- a/test/test_bitstream.py +++ b/test/test_bitstream.py @@ -4,11 +4,13 @@ from voicechat_modem_dsp.encoders.bitstream import * +@pytest.mark.unit def test_unit_bitstream_read(): bitstream=b"\x55\x55" for i in range(8*len(bitstream)): assert read_bitstream(bitstream,i)==bool(i%2) +@pytest.mark.unit def test_unit_bitstream_read_iterator(): bitstream=b"\x00\xff\x55" list_bits=list(read_bitstream_iterator(bitstream)) @@ -19,6 +21,7 @@ def test_unit_bitstream_read_iterator(): False,True,False,True, False,True,False,True] +@pytest.mark.unit def test_unit_bitstream_write(): bitstream=bytearray(b"\x00\x00") for i in range(8*len(bitstream)): @@ -26,6 +29,7 @@ def test_unit_bitstream_write(): for element in bitstream: assert element==255 +@pytest.mark.unit def test_unit_bitstream_range(): with pytest.raises(ValueError): read_bitstream(b"",1) @@ -36,6 +40,7 @@ def test_unit_bitstream_range(): with pytest.raises(ValueError): write_bitstream(bytearray(b"anything"),-1,True) +@pytest.mark.unit def test_unit_write_bitstream_type(): with pytest.raises(TypeError): write_bitstream(b"Not mutable",0,True) diff --git a/test/test_ecc.py b/test/test_ecc.py index 18a1f09..000538f 100644 --- a/test/test_ecc.py +++ b/test/test_ecc.py @@ -1,8 +1,11 @@ import random +import pytest + from voicechat_modem_dsp.encoders.bitstream import * from voicechat_modem_dsp.encoders.ecc.hamming_7_4 import * +@pytest.mark.property def test_property_corrupt_hamming_nonmangle(): for _ in range(64): n=random.randint(1,256) @@ -11,6 +14,7 @@ def test_property_corrupt_hamming_nonmangle(): recovered_perfect=hamming_decode_7_4(hamming_data_test) assert data_test==recovered_perfect +@pytest.mark.property def test_property_corrupt_hamming_recoverable_err(): for _ in range(64): n=random.randint(1,256) diff --git a/test/test_encoders.py b/test/test_encoders.py index 02a59b8..48d24a6 100644 --- a/test/test_encoders.py +++ b/test/test_encoders.py @@ -4,6 +4,7 @@ from voicechat_modem_dsp.encoders.encode_pad import * +@pytest.mark.unit def test_unit_bad_datastream_len(): datastream_singular=[0] with pytest.raises(ValueError,match=r".*length"): @@ -20,6 +21,7 @@ def test_unit_bad_datastream_len(): base_64_decode(datastream_singular) # Base256 cannot have bad length +@pytest.mark.unit def test_unit_bad_datastream(): datastream_bad_align=[0,1,2,3,-4,5,6,100] with pytest.raises(ValueError,match=r"Illegal symbol.*"): @@ -41,6 +43,7 @@ def test_unit_bad_datastream(): with pytest.raises(ValueError): base_8_decode(datastream_malform_base_8) +@pytest.mark.unit def test_unit_base_2(): bitstream=b"\x0f" bitstream_list=[0,0,0,0,1,1,1,1] @@ -50,6 +53,7 @@ def test_unit_base_2(): decode_reverse=base_2_decode(bitstream_list) assert decode_reverse==b"\xf0" +@pytest.mark.property def test_property_base_2_turnaround(): for _ in range(64): n=random.randint(1,256) @@ -58,6 +62,7 @@ def test_property_base_2_turnaround(): data_bitstream_recover=base_2_decode(data_datastream) assert bytes(data_bitstream)==data_bitstream_recover +@pytest.mark.unit def test_unit_base_4(): bitstream=b"\x0f\x81" bitstream_list=[0,0,3,3,2,0,0,1] @@ -67,6 +72,7 @@ def test_unit_base_4(): decode_reverse=base_4_decode(bitstream_list) assert decode_reverse==b"\x42\xf0" +@pytest.mark.property def test_property_base_4_turnaround(): for _ in range(64): n=random.randint(1,256) @@ -75,6 +81,7 @@ def test_property_base_4_turnaround(): data_bitstream_recover=base_4_decode(data_datastream) assert bytes(data_bitstream)==data_bitstream_recover +@pytest.mark.property def test_property_base_8_turnaround(): for _ in range(64): n=random.randint(1,256) @@ -83,6 +90,7 @@ def test_property_base_8_turnaround(): data_bitstream_recover=base_8_decode(data_datastream) assert bytes(data_bitstream)==data_bitstream_recover +@pytest.mark.unit def test_unit_base_16(): bitstream=b"\x01\x23\x45\x67\x89\xab\xcd\xef" bitstream_list=list(range(16)) @@ -92,6 +100,7 @@ def test_unit_base_16(): decode_reverse=base_16_decode(bitstream_list) assert decode_reverse==b"\xfe\xdc\xba\x98\x76\x54\x32\x10" +@pytest.mark.property def test_property_base_16_turnaround(): for _ in range(64): n=random.randint(1,256) @@ -100,6 +109,7 @@ def test_property_base_16_turnaround(): data_bitstream_recover=base_16_decode(data_datastream) assert bytes(data_bitstream)==data_bitstream_recover +@pytest.mark.property def test_property_base_32_turnaround(): for _ in range(64): n=random.randint(1,256) @@ -108,6 +118,7 @@ def test_property_base_32_turnaround(): data_bitstream_recover=base_32_decode(data_datastream) assert bytes(data_bitstream)==data_bitstream_recover +@pytest.mark.property def test_property_base_64_turnaround(): for _ in range(64): n=random.randint(1,256) @@ -116,6 +127,7 @@ def test_property_base_64_turnaround(): data_bitstream_recover=base_64_decode(data_datastream) assert bytes(data_bitstream)==data_bitstream_recover +@pytest.mark.unit def test_unit_base_256(): bitstream=b"\x01\x23\x45\x67\x89\xab\xcd\xef" bitstream_list=[0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef] @@ -125,6 +137,7 @@ def test_unit_base_256(): decode_reverse=base_256_decode(bitstream_list) assert decode_reverse==b"\xef\xcd\xab\x89\x67\x45\x23\x01" +@pytest.mark.property def test_property_base_256_turnaround(): for _ in range(64): n=random.randint(1,256) diff --git a/test/test_modulator_integrity.py b/test/test_modulator_integrity.py index 65ed23c..fa8fe53 100644 --- a/test/test_modulator_integrity.py +++ b/test/test_modulator_integrity.py @@ -11,6 +11,7 @@ def get_rand_float(lower, upper): return random.random()*(upper-lower)+lower +@pytest.mark.unit def test_unit_ask_integrity_novoice(): amplitude_list=list(np.geomspace(0.1,1,16)) list_data=list(range(256)) @@ -28,6 +29,7 @@ def test_unit_ask_integrity_novoice(): assert bitstream==recovered_bitstream +@pytest.mark.unit def test_unit_ask_integrity_voice(): amplitude_list=list(np.geomspace(0.1,1,16)) # Shuffle as opposed to complete random to test all 0x00-0xff @@ -46,6 +48,7 @@ def test_unit_ask_integrity_voice(): assert bitstream==recovered_bitstream +@pytest.mark.property def test_property_ask_integrity(): amplitude_list=list(np.linspace(0.1,1,16)) count_run=0 diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index 8e8c4cd..78575bb 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -7,6 +7,7 @@ epsilon=1e-14 +@pytest.mark.property def test_property_generate_timearray(): for _ in range(256): n=random.randint(1,50000) @@ -15,6 +16,7 @@ def test_property_generate_timearray(): assert all(np.abs(np.diff(timearr)-1/n) < epsilon) assert np.abs(timearr[-1]-1) < epsilon +@pytest.mark.unit def test_unit_average_int(): dataseq=np.asarray([1,1,4,1]) assert average_interval_data(dataseq,0,3)==2 @@ -23,19 +25,23 @@ def test_unit_average_int(): assert average_interval_data(dataseq,1,2)==2.5 assert average_interval_data(dataseq,2,3)==2.5 +@pytest.mark.unit def test_unit_average_interp(): dataseq=[3,1,0,5] assert np.abs(average_interval_data(dataseq,0.5,0.5)-2) Date: Sat, 14 Dec 2019 13:43:33 -0500 Subject: [PATCH 066/228] Run pipenv update to update dependencies --- Pipfile.lock | 259 +++++++++++++++++++++++++-------------------------- 1 file changed, 125 insertions(+), 134 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 56aa83c..03f0cca 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -38,10 +38,10 @@ }, "ipython": { "hashes": [ - "sha256:dfd303b270b7b5232b3d08bd30ec6fd685d8a58cabd54055e3d69d8f029f7280", - "sha256:ed7ebe1cba899c1c3ccad6f7f1c2d2369464cc77dba8eebc65e2043e19cda995" + "sha256:190a279bd3d4fc585a611e9358a88f1048cc57fd688254a86f9461889ee152a6", + "sha256:762d79a62b6aa96b04971e920543f558dfbeedc0468b899303c080c8068d4ac2" ], - "version": "==7.9.0" + "version": "==7.10.2" }, "ipython-genutils": { "hashes": [ @@ -122,34 +122,33 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4", - "sha256:e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31", - "sha256:f15af68f66e664eaa559d4ac8a928111eebd5feda0c11738b5998045224829db" + "sha256:0278d2f51b5ceba6ea8da39f76d15684e84c996b325475f6e5720edc584326a7", + "sha256:63daee79aa8366c8f1c637f1a4876b890da5fc92a19ebd2f7080ebacb901e990" ], - "version": "==2.0.10" + "version": "==3.0.2" }, "protobuf": { "hashes": [ - "sha256:125713564d8cfed7610e52444c9769b8dcb0b55e25cc7841f2290ee7bc86636f", - "sha256:1accdb7a47e51503be64d9a57543964ba674edac103215576399d2d0e34eac77", - "sha256:27003d12d4f68e3cbea9eb67427cab3bfddd47ff90670cb367fcd7a3a89b9657", - "sha256:3264f3c431a631b0b31e9db2ae8c927b79fc1a7b1b06b31e8e5bcf2af91fe896", - "sha256:3c5ab0f5c71ca5af27143e60613729e3488bb45f6d3f143dc918a20af8bab0bf", - "sha256:45dcf8758873e3f69feab075e5f3177270739f146255225474ee0b90429adef6", - "sha256:56a77d61a91186cc5676d8e11b36a5feb513873e4ae88d2ee5cf530d52bbcd3b", - "sha256:5984e4947bbcef5bd849d6244aec507d31786f2dd3344139adc1489fb403b300", - "sha256:6b0441da73796dd00821763bb4119674eaf252776beb50ae3883bed179a60b2a", - "sha256:6f6677c5ade94d4fe75a912926d6796d5c71a2a90c2aeefe0d6f211d75c74789", - "sha256:84a825a9418d7196e2acc48f8746cf1ee75877ed2f30433ab92a133f3eaf8fbe", - "sha256:b842c34fe043ccf78b4a6cf1019d7b80113707d68c88842d061fa2b8fb6ddedc", - "sha256:ca33d2f09dae149a1dcf942d2d825ebb06343b77b437198c9e2ef115cf5d5bc1", - "sha256:cc9af00df3fc9302f537a8335668c20be27916b2277e9a5eaed510266e2bb33b", - "sha256:db83b5c12c0cd30150bb568e6feb2435c49ce4e68fe2d7b903113f0e221e58fe", - "sha256:f50f3b1c5c1c1334ca7ce9cad5992f098f460ffd6388a3cabad10b66c2006b09", - "sha256:f99f127909731cafb841c52f9216e447d3e4afb99b17bebfad327a75aee206de" + "sha256:0265379852b9e1f76af6d3d3fe4b3c383a595cc937594bda8565cf69a96baabd", + "sha256:200b77e51f17fbc1d3049045f5835f60405dec3a00fe876b9b986592e46d908c", + "sha256:29bd1ed46b2536ad8959401a2f02d2d7b5a309f8e97518e4f92ca6c5ba74dbed", + "sha256:3175d45698edb9a07c1a78a1a4850e674ce8988f20596580158b1d0921d0f057", + "sha256:34a7270940f86da7a28be466ac541c89b6dbf144a6348b9cf7ac6f56b71006ce", + "sha256:38cbc830a4a5ba9956763b0f37090bfd14dd74e72762be6225de2ceac55f4d03", + "sha256:665194f5ad386511ac8d8a0bd57b9ab37b8dd2cd71969458777318e774b9cd46", + "sha256:839bad7d115c77cdff29b488fae6a3ab503ce9a4192bd4c42302a6ea8e5d0f33", + "sha256:934a9869a7f3b0d84eca460e386fba1f7ba2a0c1a120a2648bc41fadf50efd1c", + "sha256:aecdf12ef6dc7fd91713a6da93a86c2f2a8fe54840a3b1670853a2b7402e77c9", + "sha256:c4e90bc27c0691c76e09b5dc506133451e52caee1472b8b3c741b7c912ce43ef", + "sha256:c65d135ea2d85d40309e268106dab02d3bea723db2db21c23ecad4163ced210b", + "sha256:c98dea04a1ff41a70aff2489610f280004831798cb36a068013eed04c698903d", + "sha256:d9049aa194378a426f0b2c784e2054565bf6f754d20fcafdee7102a6250556e8", + "sha256:e028fee51c96de4e81924484c77111dfdea14010ecfc906ea5b252209b0c4de6", + "sha256:e84ad26fb50091b1ea676403c0dd2bd47663099454aa6d88000b1dafecab0941", + "sha256:e88a924b591b06d0191620e9c8aa75297b3111066bb09d49a24bae1054a10c13" ], "index": "pypi", - "version": "==3.10.0" + "version": "==3.11.1" }, "ptyprocess": { "hashes": [ @@ -160,10 +159,10 @@ }, "pygments": { "hashes": [ - "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", - "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" + "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", + "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" ], - "version": "==2.4.2" + "version": "==2.5.2" }, "python-dateutil": { "hashes": [ @@ -207,30 +206,30 @@ }, "scipy": { "hashes": [ - "sha256:0359576d8cc058bd615999cf985e2423dc6cc824666d60e8b8d4810569a04655", - "sha256:07673b5b96dbe28c88f3a53ca9af67f802aa853de7402e31f473b4dd6501c799", - "sha256:0f81e71149539ac09053a3f9165659367b060eceef3bbde11e6600e1c341f1f2", - "sha256:125aa82f7b3d4bd7f77fed6c3c6e31be47e33f129d829799569389ae59f913e7", - "sha256:2dc26e5b3eb86b7adad506b6b04020f6a87e1102c9acd039e937d28bdcee7fa6", - "sha256:2e4b5fdb635dd425bf46fbd6381612692d3c795f1eb6fe62410305a440691d46", - "sha256:33ac3213ee617bbc0eac84d02b130d69093ed7738afb281dfdeb12a9dbdf1530", - "sha256:34c48d922760782732d6f8f4532e320984d1280763c6787c6582021d34c8ad79", - "sha256:3f556f63e070e9596624e42e99d23b259d8f0fc63ec093bef97a9f1c579565b2", - "sha256:470d8fc76ccab6cfff60a9de4ce316a23ee7f63615d948c7446dc7c1bb45042d", - "sha256:4ad7a3ae9831d2085d6f50b81bfcd76368293eafdf31f4ac9f109c6061309c24", - "sha256:61812a7db0d9bc3f13653e52b8ddb1935cf444ec55f39160fc2778aeb2719057", - "sha256:7a0477929e6f9d5928fe81fe75d00b7da9545a49109e66028d85848b18aeef99", - "sha256:9c3221039da50f3b60da70b65d6b020ea26cefbb097116cfec696010432d1f6c", - "sha256:a03939b431994289f39373c57bbe452974a7da724ae7f9620a1beee575434da4", - "sha256:df4dbd3d40db3f667e0145dba5f50954bf28b2dd5b8b400c79d5e3fe8cb67ce2", - "sha256:e837c8068bd1929a533e9d51562faf6584ddb5303d9e218d8c11aa4719dcd617", - "sha256:ecfd45ca0ce1d6c13bef17794b4052cc9a9574f4be8d44c9bcfd7e34294bd2d7", - "sha256:ee5888c62cd83c9bf9927ffcee08434e7d5c81a8f31e5b85af5470e511022c08", - "sha256:f018892621b787b9abf76d51d1f0c21611c71752ebb1891ccf7992e0bf973708", - "sha256:f2d5db81d90d14a32d4aff920f52fca5639bcaaaf87b4f61bce83a1d238f49fc" + "sha256:0b8c9dc042b9a47912b18b036b4844029384a5b8d89b64a4901ac3e06876e5f6", + "sha256:18ad034be955df046b5a27924cdb3db0e8e1d76aaa22c635403fe7aee17f1482", + "sha256:225d0b5e140bb66df23d438c7b535303ce8e533f94454f4e5bde5f8d109103ea", + "sha256:2f690ba68ed7caa7c30b6dc48c1deed22c78f3840fa4736083ef4f2bd8baa19e", + "sha256:4b8746f4a755bdb2eeb39d6e253a60481e165cfd74fdfb54d27394bd2c9ec8ac", + "sha256:4ba2ce1a58fe117e993cf316a149cf9926c7c5000c0cdc4bc7c56ae8325612f6", + "sha256:546f0dc020b155b8711159d53c87b36591d31f3327c47974a4fb6b50d91589c2", + "sha256:583f2ccd6a112656c9feb2345761d2b19e9213a094cfced4e7d2c1cae4173272", + "sha256:64bf4e8ae0db2d42b58477817f648d81e77f0b381d0ea4427385bba3f959380a", + "sha256:7be424ee09bed7ced36c9457f99c826ce199fd0c0f5b272cf3d098ff7b29e3ae", + "sha256:869465c7ff89fc0a1e2ea1642b0c65f1b3c05030f3a4c0d53d6a57b2dba7c242", + "sha256:884e619821f47eccd42979488d10fa1e15dbe9f3b7660b1c8c928d203bd3c1a3", + "sha256:a42b0d02150ef4747e225c31c976a304de5dc8202ec35a27111b7bb8176e5f13", + "sha256:a70308bb065562afb936c963780deab359966d71ab4f230368b154dde3136ea4", + "sha256:b01ea5e4cf95a93dc335089f8fbe97852f56fdb74afff238cbdf09793103b6b7", + "sha256:b7b8cf45f9a48f23084f19deb9384a1cccb5e92fbc879b12f97dc4d56fb2eb92", + "sha256:bb0899d3f8b9fe8ef95b79210cf0deb6709542889fadaa438eeb3a28001e09e7", + "sha256:c008f1b58f99f1d1cc546957b3effe448365e0a217df1f1894e358906e91edad", + "sha256:cfee99d085d562a7e3c4afe51ac1fe9b434363489e565a130459307f30077973", + "sha256:dfcb0f0a2d8e958611e0b56536285bb435f03746b6feac0e29f045f7c6caf164", + "sha256:f5d47351aeb1cb6bda14a8908e56648926a6b2d714f89717c71f7ada41282141" ], "index": "pypi", - "version": "==1.3.2" + "version": "==1.3.3" }, "six": { "hashes": [ @@ -274,13 +273,6 @@ ], "version": "==0.7.12" }, - "atomicwrites": { - "hashes": [ - "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", - "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" - ], - "version": "==1.3.0" - }, "attrs": { "hashes": [ "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", @@ -304,10 +296,10 @@ }, "certifi": { "hashes": [ - "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", - "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" ], - "version": "==2019.9.11" + "version": "==2019.11.28" }, "chardet": { "hashes": [ @@ -318,41 +310,40 @@ }, "coverage": { "hashes": [ - "sha256:08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", - "sha256:0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", - "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", - "sha256:19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", - "sha256:23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", - "sha256:245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", - "sha256:331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", - "sha256:386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", - "sha256:3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", - "sha256:60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", - "sha256:63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", - "sha256:6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", - "sha256:6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", - "sha256:7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", - "sha256:826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", - "sha256:93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", - "sha256:9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", - "sha256:af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", - "sha256:bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", - "sha256:bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", - "sha256:c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", - "sha256:dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", - "sha256:df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", - "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", - "sha256:e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", - "sha256:e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", - "sha256:eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", - "sha256:eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", - "sha256:ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", - "sha256:efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", - "sha256:fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", - "sha256:ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025" + "sha256:0cd13a6e98c37b510a2d34c8281d5e1a226aaf9b65b7d770ef03c63169965351", + "sha256:1a4b6b6a2a3a6612e6361130c2cc3dc4378d8c221752b96167ccbad94b47f3cd", + "sha256:2ee55e6dba516ddf6f484aa83ccabbb0adf45a18892204c23486938d12258cde", + "sha256:3be5338a2eb4ef03c57f20917e1d12a1fd10e3853fed060b6d6b677cb3745898", + "sha256:44b783b02db03c4777d8cf71bae19eadc171a6f2a96777d916b2c30a1eb3d070", + "sha256:475bf7c4252af0a56e1abba9606f1e54127cdf122063095c75ab04f6f99cf45e", + "sha256:47c81ee687eafc2f1db7f03fbe99aab81330565ebc62fb3b61edfc2216a550c8", + "sha256:4a7f8e72b18f2aca288ff02255ce32cc830bc04d993efbc87abf6beddc9e56c0", + "sha256:50197163a22fd17f79086e087a787883b3ec9280a509807daf158dfc2a7ded02", + "sha256:56b13000acf891f700f5067512b804d1ec8c301d627486c678b903859d07f798", + "sha256:79388ae29c896299b3567965dbcd93255f175c17c6c7bca38614d12718c47466", + "sha256:79fd5d3d62238c4f583b75d48d53cdae759fe04d4fb18fe8b371d88ad2b6f8be", + "sha256:7fe3e2fde2bf1d7ce25ebcd2d3de3650b8d60d9a73ce6dcef36e20191291613d", + "sha256:81042a24f67b96e4287774014fa27220d8a4d91af1043389e4d73892efc89ac6", + "sha256:81326f1095c53111f8afc95da281e1414185f4a538609a77ca50bdfa39a6c207", + "sha256:8873dc0d8f42142ea9f20c27bbdc485190fff93823c6795be661703369e5877d", + "sha256:88d2cbcb0a112f47eef71eb95460b6995da18e6f8ca50c264585abc2c473154b", + "sha256:91f2491aeab9599956c45a77c5666d323efdec790bfe23fcceafcd91105d585a", + "sha256:979daa8655ae5a51e8e7a24e7d34e250ae8309fd9719490df92cbb2fe2b0422b", + "sha256:9c871b006c878a890c6e44a5b2f3c6291335324b298c904dc0402ee92ee1f0be", + "sha256:a6d092545e5af53e960465f652e00efbf5357adad177b2630d63978d85e46a72", + "sha256:b5ed7837b923d1d71c4f587ae1539ccd96bfd6be9788f507dbe94dab5febbb5d", + "sha256:ba259f68250f16d2444cbbfaddaa0bb20e1560a4fdaad50bece25c199e6af864", + "sha256:be1d89614c6b6c36d7578496dc8625123bda2ff44f224cf8b1c45b810ee7383f", + "sha256:c1b030a79749aa8d1f1486885040114ee56933b15ccfc90049ba266e4aa2139f", + "sha256:c95bb147fab76f2ecde332d972d8f4138b8f2daee6c466af4ff3b4f29bd4c19e", + "sha256:d52c1c2d7e856cecc05aa0526453cb14574f821b7f413cc279b9514750d795c1", + "sha256:d609a6d564ad3d327e9509846c2c47f170456344521462b469e5cb39e48ba31c", + "sha256:e1bad043c12fb58e8c7d92b3d7f2f49977dcb80a08a6d1e7a5114a11bf819fca", + "sha256:e5a675f6829c53c87d79117a8eb656cc4a5f8918185a32fc93ba09778e90f6db", + "sha256:fec32646b98baf4a22fdceb08703965bd16dea09051fbeb31a04b5b6e72b846c" ], "index": "pypi", - "version": "==4.5.4" + "version": "==5.0" }, "decorator": { "hashes": [ @@ -399,11 +390,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", - "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" + "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45", + "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f" ], "markers": "python_version < '3.8'", - "version": "==0.23" + "version": "==1.3.0" }, "ipython-genutils": { "hashes": [ @@ -429,10 +420,10 @@ }, "jsonschema": { "hashes": [ - "sha256:2fa0684276b6333ff3c0b1b27081f4b2305f0a36cf702a23db50edb141893c3f", - "sha256:94c0a13b4a0616458b42529091624e66700a17f847453e52279e35509a5b7631" + "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", + "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" ], - "version": "==3.1.1" + "version": "==3.2.0" }, "jupyter-core": { "hashes": [ @@ -483,31 +474,31 @@ }, "more-itertools": { "hashes": [ - "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", - "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", + "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564" ], - "version": "==7.2.0" + "version": "==8.0.2" }, "mypy": { "hashes": [ - "sha256:1521c186a3d200c399bd5573c828ea2db1362af7209b2adb1bb8532cea2fb36f", - "sha256:31a046ab040a84a0fc38bc93694876398e62bc9f35eca8ccbf6418b7297f4c00", - "sha256:3b1a411909c84b2ae9b8283b58b48541654b918e8513c20a400bb946aa9111ae", - "sha256:48c8bc99380575deb39f5d3400ebb6a8a1cb5cc669bbba4d3bb30f904e0a0e7d", - "sha256:540c9caa57a22d0d5d3c69047cc9dd0094d49782603eb03069821b41f9e970e9", - "sha256:672e418425d957e276c291930a3921b4a6413204f53fe7c37cad7bc57b9a3391", - "sha256:6ed3b9b3fdc7193ea7aca6f3c20549b377a56f28769783a8f27191903a54170f", - "sha256:9371290aa2cad5ad133e4cdc43892778efd13293406f7340b9ffe99d5ec7c1d9", - "sha256:ace6ac1d0f87d4072f05b5468a084a45b4eda970e4d26704f201e06d47ab2990", - "sha256:b428f883d2b3fe1d052c630642cc6afddd07d5cd7873da948644508be3b9d4a7", - "sha256:d5bf0e6ec8ba346a2cf35cb55bf4adfddbc6b6576fcc9e10863daa523e418dbb", - "sha256:d7574e283f83c08501607586b3167728c58e8442947e027d2d4c7dcd6d82f453", - "sha256:dc889c84241a857c263a2b1cd1121507db7d5b5f5e87e77147097230f374d10b", - "sha256:f4748697b349f373002656bf32fede706a0e713d67bfdcf04edf39b1f61d46eb" + "sha256:02d9bdd3398b636723ecb6c5cfe9773025a9ab7f34612c1cde5c7f2292e2d768", + "sha256:088f758a50af31cf8b42688118077292370c90c89232c783ba7979f39ea16646", + "sha256:28e9fbc96d13397a7ddb7fad7b14f373f91b5cff538e0772e77c270468df083c", + "sha256:30e123b24931f02c5d99307406658ac8f9cd6746f0d45a3dcac2fe5fbdd60939", + "sha256:3294821b5840d51a3cd7a2bb63b40fc3f901f6a3cfb3c6046570749c4c7ef279", + "sha256:41696a7d912ce16fdc7c141d87e8db5144d4be664a0c699a2b417d393994b0c2", + "sha256:4f42675fa278f3913340bb8c3371d191319704437758d7c4a8440346c293ecb2", + "sha256:54d205ccce6ed930a8a2ccf48404896d456e8b87812e491cb907a355b1a9c640", + "sha256:6992133c95a2847d309b4b0c899d7054adc60481df6f6b52bb7dee3d5fd157f7", + "sha256:6ecbd0e8e371333027abca0922b0c2c632a5b4739a0c61ffbd0733391e39144c", + "sha256:83fa87f556e60782c0fc3df1b37b7b4a840314ba1ac27f3e1a1e10cb37c89c17", + "sha256:c87ac7233c629f305602f563db07f5221950fe34fe30af072ac838fa85395f78", + "sha256:de9ec8dba773b78c49e7bec9a35c9b6fc5235682ad1fc2105752ae7c22f4b931", + "sha256:f385a0accf353ca1bca4bbf473b9d83ed18d923fdb809d3a70a385da23e25b6a" ], "index": "pypi", "markers": "python_version >= '3.5'", - "version": "==0.740" + "version": "==0.750" }, "mypy-extensions": { "hashes": [ @@ -532,11 +523,11 @@ }, "nbsphinx": { "hashes": [ - "sha256:577a9f26c39fbe0d8eda18940b13661a9b7b4b12c7127bbecdc7edf72b3c4426", - "sha256:c39b8b4d7f3c7b02ba608be3508414c033906d40caa8b6fcfef81c9c8527c47a" + "sha256:16fad6f2a7191972c98df84e76b78f86161f859682e44107668384f916aee00d", + "sha256:2d28bde42b22b66ea8699aa8275547baa524c296ee18d440448b7ab000f96f65" ], "index": "pypi", - "version": "==0.4.3" + "version": "==0.5.0" }, "packaging": { "hashes": [ @@ -553,10 +544,10 @@ }, "pluggy": { "hashes": [ - "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", - "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34" + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], - "version": "==0.13.0" + "version": "==0.13.1" }, "py": { "hashes": [ @@ -567,10 +558,10 @@ }, "pygments": { "hashes": [ - "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", - "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297" + "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", + "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" ], - "version": "==2.4.2" + "version": "==2.5.2" }, "pyparsing": { "hashes": [ @@ -581,17 +572,17 @@ }, "pyrsistent": { "hashes": [ - "sha256:eb6545dbeb1aa69ab1fb4809bfbf5a8705e44d92ef8fc7c2361682a47c46c778" + "sha256:f3b280d030afb652f79d67c5586157c5c1355c9a58dfc7940566e28d28f3df1b" ], - "version": "==0.15.5" + "version": "==0.15.6" }, "pytest": { "hashes": [ - "sha256:8e256fe71eb74e14a4d20a5987bb5e1488f0511ee800680aaedc62b9358714e8", - "sha256:ff0090819f669aaa0284d0f4aad1a6d9d67a6efdc6dd4eb4ac56b704f890a0d6" + "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa", + "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4" ], "index": "pypi", - "version": "==5.2.4" + "version": "==5.3.2" }, "pytz": { "hashes": [ @@ -623,11 +614,11 @@ }, "sphinx": { "hashes": [ - "sha256:31088dfb95359384b1005619827eaee3056243798c62724fd3fa4b84ee4d71bd", - "sha256:52286a0b9d7caa31efee301ec4300dbdab23c3b05da1c9024b4e84896fb73d79" + "sha256:3b16e48e791a322d584489ab28d8800652123d1fbfdd173e2965a31d40bf22d7", + "sha256:559c1a8ed1365a982f77650720b41114414139a635692a23c2990824d0a84cf2" ], "index": "pypi", - "version": "==2.2.1" + "version": "==2.2.2" }, "sphinxcontrib-applehelp": { "hashes": [ From ed862d1c4ea9c99c651eb72ac973a95c865b7b3d Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 14 Dec 2019 18:37:59 -0500 Subject: [PATCH 067/228] Sigma calculation now uses actual halfway point to reflected components 1/2(2 omega_c) is usually omega_c but this is not the case for Nyquist-shifted parts --- voicechat_modem_dsp/modulators/modulator_ask.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 70b27af..9a4fa0e 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -39,8 +39,12 @@ def __init__(self, fs, carrier, amp_list, baud): def _calculate_sigma(self): #sigma_t = w/4k, at most half of the pulse is smoothed away gaussian_sigma_t=(1/self.baud)/(4*ASKModulator.sigma_mult_t) - #Ensure dropoff at carrier frequency is -80dB - gaussian_sigma_f=ASKModulator.sigma_mult_f/(2*np.pi*self.carrier_freq) + # Ensure dropoff at halfway to doubled carrier frequency is -80dB + # This is not the same as carrier_freq because Nyquist reflections + doubled_carrier_refl=min( + 2*self.carrier_freq,self.fs-2*self.carrier_freq) + halfway_thresh=0.5*doubled_carrier_refl + gaussian_sigma_f=ASKModulator.sigma_mult_f/(2*np.pi*halfway_thresh) return min(gaussian_sigma_t,gaussian_sigma_f) # Expose modulator_utils calculation here as OOP read-only property From e79ae2ed180ee39d5d40f371eab71604aee7ec2b Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 15 Dec 2019 10:12:13 -0500 Subject: [PATCH 068/228] Add the beginning of a notebook describing Gaussian smoothing --- docs/index.rst | 1 + .../Pre-Modulation Gaussian Smoothing.ipynb | 224 ++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb diff --git a/docs/index.rst b/docs/index.rst index 2f2bf51..8391cd3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,6 +11,7 @@ Welcome to Voicechat Modem (DSP Component)'s documentation! :caption: Contents: notebooks/Trapezoidal Averaging + notebooks/Pre-Modulation Gaussian Smoothing Indices and tables ================== diff --git a/docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb b/docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb new file mode 100644 index 0000000..efed97f --- /dev/null +++ b/docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb @@ -0,0 +1,224 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Parameters for the Gaussian Kernel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Gaussian kernel is often used as a smoothing filter. Because it is a lowpass kernel that is an eigenfunction under the Fourier transform, it lowpass filters without introducing ripple in either the time domain or the frequency domain. In `voicechat-modem-dsp`, we use Gaussian smoothing to remove high-frequency components from the datastream and to prevent Gibbs ringing upon lowpass reconstruction." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from scipy import signal\n", + "from scipy.stats import norm" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext itikz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The typical window size for a Gaussian kernel is $6\\sigma+1$ for integer $\\sigma$, so we generalize this for all positive $\\sigma$ as shown below (making sure that $w$ remains odd to avoid scalloping):\n", + "\n", + "$$\n", + "w=\n", + "\\begin{cases}\n", + "\\lceil 6\\sigma+1 \\rceil & \\lceil 6\\sigma+1 \\rceil \\equiv 1 \\bmod 2 \\\\\n", + "\\lceil 6\\sigma+1 \\rceil + 1 & \\lceil 6\\sigma+1 \\rceil \\equiv 0 \\bmod 2\n", + "\\end{cases}\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "Computes a gaussian smoothing filter given sampling rate and sigma time\n", + "\"\"\"\n", + "def gaussian_window(fs, sigma_dt):\n", + " sigma=sigma_dt*fs\n", + " sample_count=np.ceil(6*sigma+1)\n", + " if sample_count%2==0:\n", + " sample_count+=1\n", + " raw_window=signal.windows.gaussian(sample_count, sigma)\n", + " raw_window_sum=np.sum(raw_window)\n", + " return raw_window/raw_window_sum" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are two criteria that we use to calculate the sigma and prevent intersymbol interference: the time-domain criterion and the frequency-domain criterion." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Time-Domain Criterion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Given a pulse with a width, we want to ensure that the total portion of the transition significantly affected by smoothing is at most half the pulse width.\n", + "\n", + "To more precisely define \"significantly affected\", we define a threshold such that, for a transition from extremes, the filtered pulse settles on the final value close enough to be unambiguously different from the neighboring symbols. We assume 256 distinct, evenly spaced symbols, leaving a band of $s\\pm\\frac{1}{512}$ in which a symbol can be recognized unambiguously.\n", + "\n", + "Below is a diagram illustrating the constraints:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "#diagram" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The usable segment $l=\\frac{w}{2}$ of the pulse is $w-2k_t \\sigma_t$. Solving for $\\sigma_t$ gives\n", + "\n", + "\\begin{align*}\n", + "\\frac{w}{2}&=w-2k_t \\sigma_t \\\\\n", + "\\frac{w}{2}&=2k_t \\sigma_t \\\\\n", + "\\sigma_t&=\\frac{w}{4k_t}\n", + "\\end{align*}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since the convolution of a Gaussian kernel with a Heaviside step is the CDF of the kernel, we wish to find the $z$-value $k_t$ such that $1-\\Phi^{-1}(k_t)=\\frac{1}{512}$:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.8856349124267573" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "norm.isf(1/512) #Inverse survival function, where survival function=1-cdf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Frequency-Domain Criterion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When the baud is small enough, we would like to be able to reduce the size of the Gaussian kernel without adversely impacting signal integrity. To ensure this, we turn to the frequency domain: since we want to filter out the $2\\omega_c$ components, we ensure that the Gaussian filter sufficiently rejects components above $\\omega_c$. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#diagram" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since $\\sigma_f=\\frac{\\omega_c}{k_f}$ and $\\sigma_t=\\frac{1}{2 \\pi \\sigma_f}$, we get that $\\sigma_t=\\frac{k_f}{2 \\pi \\omega_c}$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use an -80dB rejection threshold to find a corresponding $z$-value $k_f$ such that $1-\\Phi^{-1}(k_f)=0.0001$:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3.7190164854556804" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "norm.isf(0.0001)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Environment (virtualenv_voicechat-modem-dsp-ciksczpw)", + "language": "python", + "name": "virtualenv_voicechat-modem-dsp-ciksczpw" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 8a8053265aa608d56e698ac69ccac68075bdc5e7 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 16:32:01 -0800 Subject: [PATCH 069/228] First draft of smoothing notebook May want to redo the plots with pgfplots --- .../Pre-Modulation Gaussian Smoothing.ipynb | 350 +++++++++++++++++- 1 file changed, 336 insertions(+), 14 deletions(-) diff --git a/docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb b/docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb index efed97f..c2e858a 100644 --- a/docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb +++ b/docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The Gaussian kernel is often used as a smoothing filter. Because it is a lowpass kernel that is an eigenfunction under the Fourier transform, it lowpass filters without introducing ripple in either the time domain or the frequency domain. In `voicechat-modem-dsp`, we use Gaussian smoothing to remove high-frequency components from the datastream and to prevent Gibbs ringing upon lowpass reconstruction." + "The Gaussian kernel is often used as a smoothing filter. Because it is a lowpass kernel that is an eigenfunction under the Fourier transform, it lowpass filters without introducing ripple in either the time domain or the frequency domain. In `voicechat-modem-dsp`, we use Gaussian smoothing to remove high-frequency components from the datastream and to prevent ringing in the lowpass reconstruction." ] }, { @@ -88,18 +88,90 @@ "source": [ "Given a pulse with a width, we want to ensure that the total portion of the transition significantly affected by smoothing is at most half the pulse width.\n", "\n", - "To more precisely define \"significantly affected\", we define a threshold such that, for a transition from extremes, the filtered pulse settles on the final value close enough to be unambiguously different from the neighboring symbols. We assume 256 distinct, evenly spaced symbols, leaving a band of $s\\pm\\frac{1}{512}$ in which a symbol can be recognized unambiguously.\n", - "\n", - "Below is a diagram illustrating the constraints:" + "To more precisely define \"significantly affected\", we define a threshold such that, for a transition from extremes, the filtered pulse settles on the final value close enough to be unambiguously different from the neighboring symbols. We assume 256 distinct, evenly spaced symbols, leaving a band of $s\\pm\\frac{1}{512}$ in which a symbol can be recognized unambiguously." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "#diagram" + "%%itikz --temp-dir\n", + "\\documentclass[tikz]{standalone}\n", + "% Source: https://tex.stackexchange.com/questions/60950/how-to-draw-cdf-of-normal-distribution-in-tikz\n", + "\\tikzset{\n", + " declare function={\n", + " %normcdf(\\x,\\m,\\s)=1/(1 + exp(-0.07056*((\\x-\\m)/\\s)^3 - 1.5976*(\\x-\\m)/\\s));\n", + " normcdf(\\x,\\m,\\s)=1/(1 + exp(-1.702*((\\x-\\m)/\\s) ));\n", + " }\n", + "}\n", + "\\def\\sigmaval{2}\n", + "\\def\\vertlineval{(1.534*\\sigmaval)}\n", + "\\begin{document}\n", + "\\begin{tikzpicture}\n", + "\n", + "\\draw[thick,<->] (-5,0) -- (5,0) coordinate [label={below: $t$}] (xmax);\n", + "\\draw[thick,->] (0,0) -- (0,4) coordinate [label={right: $y$}] (ymax);\n", + "\n", + "\\draw[blue,domain=-5:5,smooth,variable=\\x] plot ({\\x},{4*(normcdf(\\x+6,0,\\sigmaval)-normcdf(\\x-6,0,\\sigmaval))});\n", + "\\draw[red] ({-\\vertlineval},0) -- ({-\\vertlineval},4);\n", + "\\draw[red] ({\\vertlineval},0) -- ({\\vertlineval},4);\n", + "\\draw[red] (-5,4/16) -- (5,4/16);\n", + "\\draw[red] (-5,4-4/16) -- (5,4-4/16);\n", + "\\end{tikzpicture}\n", + "\\end{document}" ] }, { @@ -119,7 +191,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Since the convolution of a Gaussian kernel with a Heaviside step is the CDF of the kernel, we wish to find the $z$-value $k_t$ such that $1-\\Phi^{-1}(k_t)=\\frac{1}{512}$:" + "Since the convolution of a Gaussian kernel with the step function is the CDF of the kernel, we wish to find the $z$-value $k_t$ such that $1-\\Phi^{-1}(k_t)=\\frac{1}{512}$:" ] }, { @@ -153,23 +225,259 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "When the baud is small enough, we would like to be able to reduce the size of the Gaussian kernel without adversely impacting signal integrity. To ensure this, we turn to the frequency domain: since we want to filter out the $2\\omega_c$ components, we ensure that the Gaussian filter sufficiently rejects components above $\\omega_c$. " + "When the baud is small enough, we would like to reduce the size of the Gaussian kernel without adversely impacting signal integrity. To ensure this, we turn to the frequency domain: since we want to filter out the $2\\omega_c$ components when doing product demodulation, we ensure that the Gaussian filter sufficiently rejects components above $\\omega_c$. (Half the demodulated carrier is not always the original carrier due to Nyquist aliasing, but we ignore that here for simplicity.)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "#diagram" + "%%itikz --temp-dir\n", + "\\documentclass[tikz]{standalone}\n", + "\\begin{document}\n", + "\\def\\omegaval{5}\n", + "\\def\\sigmaval{(5/3.719)}\n", + "\\def\\sidebandwidth{1}\n", + "\\begin{tikzpicture}\n", + "%Axes\n", + "\\node[align=center] at (6,4.5) {Frequency-Domain View of Product Demodulated Signal};\n", + "\\draw[thick, ->] (0,0) -- (12,0) coordinate [label={below: $\\omega$}] (xmax);\n", + "\\draw[thick, ->] (0,0) -- (0,4) coordinate [label={left: $y$}] (ymax);\n", + "\n", + "%Frequency labels\n", + "\\coordinate[label={below:$\\omega_c$}] (w_c) at (\\omegaval,0) {};\n", + "\\coordinate[label={below:$2\\omega_c$}] (two_w_c) at (2*\\omegaval,0) {};\n", + "\n", + "%omega_c and related vertical lines\n", + "\\draw[red, dashed] (\\omegaval,0) -- (\\omegaval,4);\n", + "\\draw[->] (2*\\omegaval,0) -- (2*\\omegaval,4);\n", + "\n", + "% Demodulated signal components\n", + "\\draw[blue] (0,3) -- (\\sidebandwidth,0);\n", + "\\draw[green] (2*\\omegaval,3) -- (2*\\omegaval-\\sidebandwidth,0);\n", + "\\draw[green] (2*\\omegaval,3) -- (2*\\omegaval+\\sidebandwidth,0);\n", + "\n", + "\\draw[domain=0:12,smooth,variable=\\x] plot ({\\x},{2*e^(-0.5*(\\x/\\sigmaval)^2)});\n", + "\\end{tikzpicture}\n", + "\\end{document}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Since $\\sigma_f=\\frac{\\omega_c}{k_f}$ and $\\sigma_t=\\frac{1}{2 \\pi \\sigma_f}$, we get that $\\sigma_t=\\frac{k_f}{2 \\pi \\omega_c}$." + "Since $\\sigma_f=\\frac{\\omega_c}{k_f}$ and $\\sigma_t=\\frac{1}{2 \\pi \\sigma_f}$, we get $\\sigma_t=\\frac{k_f}{2 \\pi \\omega_c}$." ] }, { @@ -181,7 +489,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -190,7 +498,7 @@ "3.7190164854556804" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -198,6 +506,20 @@ "source": [ "norm.isf(0.0001)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use these two criteria to calculate an appropriate $\\sigma$ value for the Gaussian kernel. We take the minimum of these two in order to ensure sufficient smoothing while preventing the kernel from growing too large." + ] } ], "metadata": { From b782365efdfeef1aab5d132517a306fa78fdaba3 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 19:59:26 -0800 Subject: [PATCH 070/228] Allow any 0-1 amplitudes that are valid Raise warning instead of error if some are too low --- voicechat_modem_dsp/modulators/modulator_ask.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 9a4fa0e..3668635 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -22,17 +22,26 @@ def __init__(self, fs, carrier, amp_list, baud): # Nyquist limit if carrier>=0.5*fs: raise ValueError("Carrier frequency is too high for sampling rate") - if any((x>1 or x<0.1 for x in amp_list)): + if any((x>1 or x<=0 for x in amp_list)): raise ValueError("Invalid amplitudes given") + self.fs=fs self.amp_list=dict(enumerate(amp_list)) self.carrier_freq=carrier self.baud=baud # Nyquist aliased 2*carrier=carrier if carrier>=(1/3)*fs: - warnings.warn("Carrier frequency is too high to guarantee" + warnings.warn("Carrier frequency is too high to guarantee " "proper lowpass reconstruction", ModulationIntegrityWarning) + if any(x<0.1 for x in amp_list): + warnings.warn("Some amplitudes may be too low " + "to be distinguishable from background noise", + ModulationIntegrityWarning) + if any(dx<=0.05 for dx in np.diff(amp_list)): + warnings.warn("Some amplitudes may be too close " + "to be distinguishable from each other", + ModulationIntegrityWarning) # TODO: additional warnings relating to filter overshoot and the like @property From d70e92e1c20e765e477159b2ac3445d1e8b0778c Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 19:59:42 -0800 Subject: [PATCH 071/228] Write tests to trigger ASKModulator errors and warnings --- test/test_modulator_parameters.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/test_modulator_parameters.py diff --git a/test/test_modulator_parameters.py b/test/test_modulator_parameters.py new file mode 100644 index 0000000..5d85c8d --- /dev/null +++ b/test/test_modulator_parameters.py @@ -0,0 +1,27 @@ +import warnings +import numpy as np + +from voicechat_modem_dsp.encoders.encode_pad import * +from voicechat_modem_dsp.modulators.modulator_ask import ASKModulator +from voicechat_modem_dsp.modulators.modulator_utils import ModulationIntegrityWarning + +import pytest + +@pytest.mark.unit +def test_invalid_nyquist(): + with pytest.raises(ValueError, match=r"Carrier frequency is too high.+"): + bad_modulator=ASKModulator(1000,600,np.linspace(0.2,1,8),20) + with pytest.raises(ValueError, match=r"Baud is too high.+"): + bad_modulator=ASKModulator(1000,100,np.linspace(0.2,1,8),80) + + with pytest.warns(ModulationIntegrityWarning): + bad_modulator=ASKModulator(900,400,np.linspace(0.2,1,8),100) + +@pytest.mark.unit +def test_invalid_modspecific(): + with pytest.raises(ValueError, match=r"Invalid amplitudes.+"): + bad_modulator=ASKModulator(1000,200,np.linspace(-1,2,16),20) + with pytest.warns(ModulationIntegrityWarning): + bad_modulator=ASKModulator(2000,880,np.geomspace(0.01,0.5,4),40) + with pytest.warns(ModulationIntegrityWarning): + bad_modulator=ASKModulator(2000,880,np.geomspace(0.2,1,256),40) \ No newline at end of file From 09e4d5bb099685176fa35b25e872577342a5be6c Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 21:14:18 -0800 Subject: [PATCH 072/228] Adjust lengths for encoder turnaround property tests --- test/test_encoders.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test_encoders.py b/test/test_encoders.py index 48d24a6..b4d82fb 100644 --- a/test/test_encoders.py +++ b/test/test_encoders.py @@ -75,7 +75,7 @@ def test_unit_base_4(): @pytest.mark.property def test_property_base_4_turnaround(): for _ in range(64): - n=random.randint(1,256) + n=random.randint(1,512) data_bitstream=bytearray((random.getrandbits(8) for _ in range(n))) data_datastream=base_4_encode(data_bitstream) data_bitstream_recover=base_4_decode(data_datastream) @@ -84,7 +84,7 @@ def test_property_base_4_turnaround(): @pytest.mark.property def test_property_base_8_turnaround(): for _ in range(64): - n=random.randint(1,256) + n=random.randint(1,1024) data_bitstream=bytearray((random.getrandbits(8) for _ in range(n))) data_datastream=base_8_encode(data_bitstream) data_bitstream_recover=base_8_decode(data_datastream) @@ -103,7 +103,7 @@ def test_unit_base_16(): @pytest.mark.property def test_property_base_16_turnaround(): for _ in range(64): - n=random.randint(1,256) + n=random.randint(1,2048) data_bitstream=bytearray((random.getrandbits(8) for _ in range(n))) data_datastream=base_16_encode(data_bitstream) data_bitstream_recover=base_16_decode(data_datastream) @@ -112,7 +112,7 @@ def test_property_base_16_turnaround(): @pytest.mark.property def test_property_base_32_turnaround(): for _ in range(64): - n=random.randint(1,256) + n=random.randint(1,4096) data_bitstream=bytearray((random.getrandbits(8) for _ in range(n))) data_datastream=base_32_encode(data_bitstream) data_bitstream_recover=base_32_decode(data_datastream) @@ -121,7 +121,7 @@ def test_property_base_32_turnaround(): @pytest.mark.property def test_property_base_64_turnaround(): for _ in range(64): - n=random.randint(1,256) + n=random.randint(1,8192) data_bitstream=bytearray((random.getrandbits(8) for _ in range(n))) data_datastream=base_64_encode(data_bitstream) data_bitstream_recover=base_64_decode(data_datastream) @@ -140,7 +140,7 @@ def test_unit_base_256(): @pytest.mark.property def test_property_base_256_turnaround(): for _ in range(64): - n=random.randint(1,256) + n=random.randint(1,32768) data_bitstream=bytearray((random.getrandbits(8) for _ in range(n))) data_datastream=base_256_encode(data_bitstream) data_bitstream_recover=base_256_decode(data_datastream) From ec8b5513d22b0ad554184a7ae079839623e7b117 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 21:15:28 -0800 Subject: [PATCH 073/228] Check filter generation error handling --- test/test_modulator_utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index 78575bb..ed5f1b1 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -16,6 +16,13 @@ def test_property_generate_timearray(): assert all(np.abs(np.diff(timearr)-1/n) < epsilon) assert np.abs(timearr[-1]-1) < epsilon +@pytest.mark.unit +def test_filtergen_error(): + with pytest.raises(ValueError): + filt=lowpass_fir_filter(2000,500,100) + with pytest.raises(ValueError): + filt=lowpass_fir_filter(2000,5000,1000) + @pytest.mark.unit def test_unit_average_int(): dataseq=np.asarray([1,1,4,1]) From 613f69f47ed2b4ed2956fa40bbb23174fa63921e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 21:15:46 -0800 Subject: [PATCH 074/228] In filter generation, check that both cutoffs are below Nyquist --- voicechat_modem_dsp/modulators/modulator_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index fbd268c..5c00bcf 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -116,6 +116,9 @@ def fred_harris_fir_tap_count(fs, transition_width, db_attenuation): def lowpass_fir_filter(fs,cutoff_low,cutoff_high,attenuation=80): if cutoff_low>=cutoff_high: raise ValueError("High cutoff must be larger than low cutoff") + if cutoff_low>=0.5*fs or cutoff_high>=0.5*fs: + raise ValueError("Cutoffs must be lower than Nyquist limit") + tap_count=fred_harris_fir_tap_count(fs,cutoff_high-cutoff_low,attenuation) # Remez would sometimes return NaN arrays as filters From 2b9a2dac568adc16a5963d67f7da83cb23542d6f Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 21:27:59 -0800 Subject: [PATCH 075/228] Disable pypy3 Travis CI builds given PermissionErrors --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d3bb983..1dd0729 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ addons: python: - "3.8" - "3.7" - - "pypy3" +# - "pypy3" - "3.6" - "3.5" From 3d8dba89091d0d52559948e86ffdc88b2883c20e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 21:29:18 -0800 Subject: [PATCH 076/228] Migrate README badge to Travis CI.com --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d0c93b..5ae7e63 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # voicechat-modem-dsp -[![Build Status](https://travis-ci.org/rlee287/voicechat-modem-dsp.svg?branch=master)](https://travis-ci.org/rlee287/voicechat-modem-dsp) +[![Build Status](https://travis-ci.com/rlee287/voicechat-modem-dsp.svg?branch=master)](https://travis-ci.com/rlee287/voicechat-modem-dsp) [![codecov](https://codecov.io/gh/rlee287/voicechat-modem-dsp/branch/master/graph/badge.svg)](https://codecov.io/gh/rlee287/voicechat-modem-dsp) [![CodeFactor](https://www.codefactor.io/repository/github/rlee287/voicechat-modem-dsp/badge)](https://www.codefactor.io/repository/github/rlee287/voicechat-modem-dsp) From d4ba2ea0914b174248cc6d3c423257c66c898282 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 21:32:21 -0800 Subject: [PATCH 077/228] Add newline to end of parameters test file --- test/test_modulator_parameters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_modulator_parameters.py b/test/test_modulator_parameters.py index 5d85c8d..0ae13d8 100644 --- a/test/test_modulator_parameters.py +++ b/test/test_modulator_parameters.py @@ -24,4 +24,4 @@ def test_invalid_modspecific(): with pytest.warns(ModulationIntegrityWarning): bad_modulator=ASKModulator(2000,880,np.geomspace(0.01,0.5,4),40) with pytest.warns(ModulationIntegrityWarning): - bad_modulator=ASKModulator(2000,880,np.geomspace(0.2,1,256),40) \ No newline at end of file + bad_modulator=ASKModulator(2000,880,np.geomspace(0.2,1,256),40) From 12b41db0bffccb4cd78202e390f22cd062bc05fe Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 22:26:37 -0800 Subject: [PATCH 078/228] Run pipenv update to update lockfile --- Pipfile.lock | 238 ++++++++++++++++++++++++++------------------------- 1 file changed, 120 insertions(+), 118 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 03f0cca..4edb300 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -52,10 +52,10 @@ }, "jedi": { "hashes": [ - "sha256:786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", - "sha256:ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e" + "sha256:1349c1e8c107095a55386628bb3b2a79422f3a2cab8381e34ce19909e0cf5064", + "sha256:e909527104a903606dd63bea6e8e888833f0ef087057829b89a18364a856f807" ], - "version": "==0.15.1" + "version": "==0.15.2" }, "jupyter-client": { "hashes": [ @@ -73,37 +73,37 @@ }, "numpy": { "hashes": [ - "sha256:0a7a1dd123aecc9f0076934288ceed7fd9a81ba3919f11a855a7887cbe82a02f", - "sha256:0c0763787133dfeec19904c22c7e358b231c87ba3206b211652f8cbe1241deb6", - "sha256:3d52298d0be333583739f1aec9026f3b09fdfe3ddf7c7028cb16d9d2af1cca7e", - "sha256:43bb4b70585f1c2d153e45323a886839f98af8bfa810f7014b20be714c37c447", - "sha256:475963c5b9e116c38ad7347e154e5651d05a2286d86455671f5b1eebba5feb76", - "sha256:64874913367f18eb3013b16123c9fed113962e75d809fca5b78ebfbb73ed93ba", - "sha256:683828e50c339fc9e68720396f2de14253992c495fdddef77a1e17de55f1decc", - "sha256:6ca4000c4a6f95a78c33c7dadbb9495c10880be9c89316aa536eac359ab820ae", - "sha256:75fd817b7061f6378e4659dd792c84c0b60533e867f83e0d1e52d5d8e53df88c", - "sha256:7d81d784bdbed30137aca242ab307f3e65c8d93f4c7b7d8f322110b2e90177f9", - "sha256:8d0af8d3664f142414fd5b15cabfd3b6cc3ef242a3c7a7493257025be5a6955f", - "sha256:9679831005fb16c6df3dd35d17aa31dc0d4d7573d84f0b44cc481490a65c7725", - "sha256:a8f67ebfae9f575d85fa859b54d3bdecaeece74e3274b0b5c5f804d7ca789fe1", - "sha256:acbf5c52db4adb366c064d0b7c7899e3e778d89db585feadd23b06b587d64761", - "sha256:ada4805ed51f5bcaa3a06d3dd94939351869c095e30a2b54264f5a5004b52170", - "sha256:c7354e8f0eca5c110b7e978034cd86ed98a7a5ffcf69ca97535445a595e07b8e", - "sha256:e2e9d8c87120ba2c591f60e32736b82b67f72c37ba88a4c23c81b5b8fa49c018", - "sha256:e467c57121fe1b78a8f68dd9255fbb3bb3f4f7547c6b9e109f31d14569f490c3", - "sha256:ede47b98de79565fcd7f2decb475e2dcc85ee4097743e551fe26cfc7eb3ff143", - "sha256:f58913e9227400f1395c7b800503ebfdb0772f1c33ff8cb4d6451c06cabdf316", - "sha256:fe39f5fd4103ec4ca3cb8600b19216cd1ff316b4990f4c0b6057ad982c0a34d5" + "sha256:03bbde29ac8fba860bb2c53a1525b3604a9b60417855ac3119d89868ec6041c3", + "sha256:1baefd1fb4695e7f2e305467dbd876d765e6edd30c522894df76f8301efaee36", + "sha256:1c35fb1131362e6090d30286cfda52ddd42e69d3e2bf1fea190a0fad83ea3a18", + "sha256:3c68c827689ca0ca713dba598335073ce0966850ec0b30715527dce4ecd84055", + "sha256:443ab93fc35b31f01db8704681eb2fd82f3a1b2fa08eed2dd0e71f1f57423d4a", + "sha256:56710a756c5009af9f35b91a22790701420406d9ac24cf6b652b0e22cfbbb7ff", + "sha256:62506e9e4d2a39c87984f081a2651d4282a1d706b1a82fe9d50a559bb58e705a", + "sha256:6f8113c8dbfc192b58996ee77333696469ea121d1c44ea429d8fd266e4c6be51", + "sha256:712f0c32555132f4b641b918bdb1fd3c692909ae916a233ce7f50eac2de87e37", + "sha256:854f6ed4fa91fa6da5d764558804ba5b0f43a51e5fe9fc4fdc93270b052f188a", + "sha256:88c5ccbc4cadf39f32193a5ef22e3f84674418a9fd877c63322917ae8f295a56", + "sha256:905cd6fa6ac14654a6a32b21fad34670e97881d832e24a3ca32e19b455edb4a8", + "sha256:9d6de2ad782aae68f7ed0e0e616477fbf693d6d7cc5f0f1505833ff12f84a673", + "sha256:a30f5c3e1b1b5d16ec1f03f4df28e08b8a7529d8c920bbed657f4fde61f1fbcd", + "sha256:a9d72d9abaf65628f0f31bbb573b7d9304e43b1e6bbae43149c17737a42764c4", + "sha256:ac3cf835c334fcc6b74dc4e630f9b5ff7b4c43f7fb2a7813208d95d4e10b5623", + "sha256:b091e5d4cbbe79f0e8b6b6b522346e54a282eadb06e3fd761e9b6fafc2ca91ad", + "sha256:cc070fc43a494e42732d6ae2f6621db040611c1dde64762a40c8418023af56d7", + "sha256:e1080e37c090534adb2dd7ae1c59ee883e5d8c3e63d2a4d43c20ee348d0459c5", + "sha256:f084d513de729ff10cd72a1f80db468cff464fedb1ef2fea030221a0f62d7ff4", + "sha256:f6a7421da632fc01e8a3ecd19c3f7350258d82501a646747664bae9c6a87c731" ], "index": "pypi", - "version": "==1.17.4" + "version": "==1.18.0" }, "parso": { "hashes": [ - "sha256:63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", - "sha256:666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c" + "sha256:55cf25df1a35fd88b878715874d2c4dc1ad3f0eebd1e0266a67e1f55efccfbe1", + "sha256:5c1f7791de6bd5dbbeac8db0ef5594b36799de198b3f7f7014643b0c5536b9d3" ], - "version": "==0.5.1" + "version": "==0.5.2" }, "pexpect": { "hashes": [ @@ -129,26 +129,28 @@ }, "protobuf": { "hashes": [ - "sha256:0265379852b9e1f76af6d3d3fe4b3c383a595cc937594bda8565cf69a96baabd", - "sha256:200b77e51f17fbc1d3049045f5835f60405dec3a00fe876b9b986592e46d908c", - "sha256:29bd1ed46b2536ad8959401a2f02d2d7b5a309f8e97518e4f92ca6c5ba74dbed", - "sha256:3175d45698edb9a07c1a78a1a4850e674ce8988f20596580158b1d0921d0f057", - "sha256:34a7270940f86da7a28be466ac541c89b6dbf144a6348b9cf7ac6f56b71006ce", - "sha256:38cbc830a4a5ba9956763b0f37090bfd14dd74e72762be6225de2ceac55f4d03", - "sha256:665194f5ad386511ac8d8a0bd57b9ab37b8dd2cd71969458777318e774b9cd46", - "sha256:839bad7d115c77cdff29b488fae6a3ab503ce9a4192bd4c42302a6ea8e5d0f33", - "sha256:934a9869a7f3b0d84eca460e386fba1f7ba2a0c1a120a2648bc41fadf50efd1c", - "sha256:aecdf12ef6dc7fd91713a6da93a86c2f2a8fe54840a3b1670853a2b7402e77c9", - "sha256:c4e90bc27c0691c76e09b5dc506133451e52caee1472b8b3c741b7c912ce43ef", - "sha256:c65d135ea2d85d40309e268106dab02d3bea723db2db21c23ecad4163ced210b", - "sha256:c98dea04a1ff41a70aff2489610f280004831798cb36a068013eed04c698903d", - "sha256:d9049aa194378a426f0b2c784e2054565bf6f754d20fcafdee7102a6250556e8", - "sha256:e028fee51c96de4e81924484c77111dfdea14010ecfc906ea5b252209b0c4de6", - "sha256:e84ad26fb50091b1ea676403c0dd2bd47663099454aa6d88000b1dafecab0941", - "sha256:e88a924b591b06d0191620e9c8aa75297b3111066bb09d49a24bae1054a10c13" + "sha256:0329e86a397db2a83f9dcbe21d9be55a47f963cdabc893c3a24f4d3a8f117c37", + "sha256:0a7219254afec0d488211f3d482d8ed57e80ae735394e584a98d8f30a8c88a36", + "sha256:14d6ac53df9cb5bb87c4f91b677c1bc5cec9c0fd44327f367a3c9562de2877c4", + "sha256:180fc364b42907a1d2afa183ccbeffafe659378c236b1ec3daca524950bb918d", + "sha256:3d7a7d8d20b4e7a8f63f62de2d192cfd8b7a53c56caba7ece95367ca2b80c574", + "sha256:3f509f7e50d806a434fe4a5fbf602516002a0f092889209fff7db82060efffc0", + "sha256:4571da974019849201fc1ec6626b9cea54bd11b6bed140f8f737c0a33ea37de5", + "sha256:557686c43fbd04f5f7c533f00feee9a37dcca7b5896e3ae3664a33864e6dd546", + "sha256:56bd1d84fbf4505c7b73f04de987eef5682e5752c811141b0186a3809bfb396f", + "sha256:680c668d00b5eff08b86aef9e5ba9a705e621ea05d39071cfea8e28cb2400946", + "sha256:6b5b947dc8b3f2aec0eaad65b0b5113fcd642c358c31357c647da6281ee31104", + "sha256:6e96dffaf4d0a9a329e528b353ba62fd9ef13599688723d96bc9c165d0b6871e", + "sha256:919f0d6f6addc836d08658eba3b52be2e92fd3e76da3ce00c325d8e9826d17c7", + "sha256:9c7b19c30cf0644afd0e4218b13f637ce54382fdcb1c8f75bf3e84e49a5f6d0a", + "sha256:a2e6f57114933882ec701807f217df2fb4588d47f71f227c0a163446b930d507", + "sha256:a6b970a2eccfcbabe1acf230fbf112face1c4700036c95e195f3554d7bcb04c1", + "sha256:bc45641cbcdea068b67438244c926f9fd3e5cbdd824448a4a64370610df7c593", + "sha256:d61b14a9090da77fe87e38ba4c6c43d3533dcbeb5d84f5474e7ac63c532dcc9c", + "sha256:d6faf5dbefb593e127463f58076b62fcfe0784187be8fe1aa9167388f24a22a1" ], "index": "pypi", - "version": "==3.11.1" + "version": "==3.11.2" }, "ptyprocess": { "hashes": [ @@ -206,30 +208,30 @@ }, "scipy": { "hashes": [ - "sha256:0b8c9dc042b9a47912b18b036b4844029384a5b8d89b64a4901ac3e06876e5f6", - "sha256:18ad034be955df046b5a27924cdb3db0e8e1d76aaa22c635403fe7aee17f1482", - "sha256:225d0b5e140bb66df23d438c7b535303ce8e533f94454f4e5bde5f8d109103ea", - "sha256:2f690ba68ed7caa7c30b6dc48c1deed22c78f3840fa4736083ef4f2bd8baa19e", - "sha256:4b8746f4a755bdb2eeb39d6e253a60481e165cfd74fdfb54d27394bd2c9ec8ac", - "sha256:4ba2ce1a58fe117e993cf316a149cf9926c7c5000c0cdc4bc7c56ae8325612f6", - "sha256:546f0dc020b155b8711159d53c87b36591d31f3327c47974a4fb6b50d91589c2", - "sha256:583f2ccd6a112656c9feb2345761d2b19e9213a094cfced4e7d2c1cae4173272", - "sha256:64bf4e8ae0db2d42b58477817f648d81e77f0b381d0ea4427385bba3f959380a", - "sha256:7be424ee09bed7ced36c9457f99c826ce199fd0c0f5b272cf3d098ff7b29e3ae", - "sha256:869465c7ff89fc0a1e2ea1642b0c65f1b3c05030f3a4c0d53d6a57b2dba7c242", - "sha256:884e619821f47eccd42979488d10fa1e15dbe9f3b7660b1c8c928d203bd3c1a3", - "sha256:a42b0d02150ef4747e225c31c976a304de5dc8202ec35a27111b7bb8176e5f13", - "sha256:a70308bb065562afb936c963780deab359966d71ab4f230368b154dde3136ea4", - "sha256:b01ea5e4cf95a93dc335089f8fbe97852f56fdb74afff238cbdf09793103b6b7", - "sha256:b7b8cf45f9a48f23084f19deb9384a1cccb5e92fbc879b12f97dc4d56fb2eb92", - "sha256:bb0899d3f8b9fe8ef95b79210cf0deb6709542889fadaa438eeb3a28001e09e7", - "sha256:c008f1b58f99f1d1cc546957b3effe448365e0a217df1f1894e358906e91edad", - "sha256:cfee99d085d562a7e3c4afe51ac1fe9b434363489e565a130459307f30077973", - "sha256:dfcb0f0a2d8e958611e0b56536285bb435f03746b6feac0e29f045f7c6caf164", - "sha256:f5d47351aeb1cb6bda14a8908e56648926a6b2d714f89717c71f7ada41282141" + "sha256:00af72998a46c25bdb5824d2b729e7dabec0c765f9deb0b504f928591f5ff9d4", + "sha256:0902a620a381f101e184a958459b36d3ee50f5effd186db76e131cbefcbb96f7", + "sha256:1e3190466d669d658233e8a583b854f6386dd62d655539b77b3fa25bfb2abb70", + "sha256:2cce3f9847a1a51019e8c5b47620da93950e58ebc611f13e0d11f4980ca5fecb", + "sha256:3092857f36b690a321a662fe5496cb816a7f4eecd875e1d36793d92d3f884073", + "sha256:386086e2972ed2db17cebf88610aab7d7f6e2c0ca30042dc9a89cf18dcc363fa", + "sha256:71eb180f22c49066f25d6df16f8709f215723317cc951d99e54dc88020ea57be", + "sha256:770254a280d741dd3436919d47e35712fb081a6ff8bafc0f319382b954b77802", + "sha256:787cc50cab3020a865640aba3485e9fbd161d4d3b0d03a967df1a2881320512d", + "sha256:8a07760d5c7f3a92e440ad3aedcc98891e915ce857664282ae3c0220f3301eb6", + "sha256:8d3bc3993b8e4be7eade6dcc6fd59a412d96d3a33fa42b0fa45dc9e24495ede9", + "sha256:9508a7c628a165c2c835f2497837bf6ac80eb25291055f56c129df3c943cbaf8", + "sha256:a144811318853a23d32a07bc7fd5561ff0cac5da643d96ed94a4ffe967d89672", + "sha256:a1aae70d52d0b074d8121333bc807a485f9f1e6a69742010b33780df2e60cfe0", + "sha256:a2d6df9eb074af7f08866598e4ef068a2b310d98f87dc23bd1b90ec7bdcec802", + "sha256:bb517872058a1f087c4528e7429b4a44533a902644987e7b2fe35ecc223bc408", + "sha256:c5cac0c0387272ee0e789e94a570ac51deb01c796b37fb2aad1fb13f85e2f97d", + "sha256:cc971a82ea1170e677443108703a2ec9ff0f70752258d0e9f5433d00dda01f59", + "sha256:dba8306f6da99e37ea08c08fef6e274b5bf8567bb094d1dbe86a20e532aca088", + "sha256:dc60bb302f48acf6da8ca4444cfa17d52c63c5415302a9ee77b3b21618090521", + "sha256:dee1bbf3a6c8f73b6b218cb28eed8dd13347ea2f87d572ce19b289d6fd3fbc59" ], "index": "pypi", - "version": "==1.3.3" + "version": "==1.4.1" }, "six": { "hashes": [ @@ -310,40 +312,40 @@ }, "coverage": { "hashes": [ - "sha256:0cd13a6e98c37b510a2d34c8281d5e1a226aaf9b65b7d770ef03c63169965351", - "sha256:1a4b6b6a2a3a6612e6361130c2cc3dc4378d8c221752b96167ccbad94b47f3cd", - "sha256:2ee55e6dba516ddf6f484aa83ccabbb0adf45a18892204c23486938d12258cde", - "sha256:3be5338a2eb4ef03c57f20917e1d12a1fd10e3853fed060b6d6b677cb3745898", - "sha256:44b783b02db03c4777d8cf71bae19eadc171a6f2a96777d916b2c30a1eb3d070", - "sha256:475bf7c4252af0a56e1abba9606f1e54127cdf122063095c75ab04f6f99cf45e", - "sha256:47c81ee687eafc2f1db7f03fbe99aab81330565ebc62fb3b61edfc2216a550c8", - "sha256:4a7f8e72b18f2aca288ff02255ce32cc830bc04d993efbc87abf6beddc9e56c0", - "sha256:50197163a22fd17f79086e087a787883b3ec9280a509807daf158dfc2a7ded02", - "sha256:56b13000acf891f700f5067512b804d1ec8c301d627486c678b903859d07f798", - "sha256:79388ae29c896299b3567965dbcd93255f175c17c6c7bca38614d12718c47466", - "sha256:79fd5d3d62238c4f583b75d48d53cdae759fe04d4fb18fe8b371d88ad2b6f8be", - "sha256:7fe3e2fde2bf1d7ce25ebcd2d3de3650b8d60d9a73ce6dcef36e20191291613d", - "sha256:81042a24f67b96e4287774014fa27220d8a4d91af1043389e4d73892efc89ac6", - "sha256:81326f1095c53111f8afc95da281e1414185f4a538609a77ca50bdfa39a6c207", - "sha256:8873dc0d8f42142ea9f20c27bbdc485190fff93823c6795be661703369e5877d", - "sha256:88d2cbcb0a112f47eef71eb95460b6995da18e6f8ca50c264585abc2c473154b", - "sha256:91f2491aeab9599956c45a77c5666d323efdec790bfe23fcceafcd91105d585a", - "sha256:979daa8655ae5a51e8e7a24e7d34e250ae8309fd9719490df92cbb2fe2b0422b", - "sha256:9c871b006c878a890c6e44a5b2f3c6291335324b298c904dc0402ee92ee1f0be", - "sha256:a6d092545e5af53e960465f652e00efbf5357adad177b2630d63978d85e46a72", - "sha256:b5ed7837b923d1d71c4f587ae1539ccd96bfd6be9788f507dbe94dab5febbb5d", - "sha256:ba259f68250f16d2444cbbfaddaa0bb20e1560a4fdaad50bece25c199e6af864", - "sha256:be1d89614c6b6c36d7578496dc8625123bda2ff44f224cf8b1c45b810ee7383f", - "sha256:c1b030a79749aa8d1f1486885040114ee56933b15ccfc90049ba266e4aa2139f", - "sha256:c95bb147fab76f2ecde332d972d8f4138b8f2daee6c466af4ff3b4f29bd4c19e", - "sha256:d52c1c2d7e856cecc05aa0526453cb14574f821b7f413cc279b9514750d795c1", - "sha256:d609a6d564ad3d327e9509846c2c47f170456344521462b469e5cb39e48ba31c", - "sha256:e1bad043c12fb58e8c7d92b3d7f2f49977dcb80a08a6d1e7a5114a11bf819fca", - "sha256:e5a675f6829c53c87d79117a8eb656cc4a5f8918185a32fc93ba09778e90f6db", - "sha256:fec32646b98baf4a22fdceb08703965bd16dea09051fbeb31a04b5b6e72b846c" + "sha256:0101888bd1592a20ccadae081ba10e8b204d20235d18d05c6f7d5e904a38fc10", + "sha256:04b961862334687549eb91cd5178a6fbe977ad365bddc7c60f2227f2f9880cf4", + "sha256:1ca43dbd739c0fc30b0a3637a003a0d2c7edc1dd618359d58cc1e211742f8bd1", + "sha256:1cbb88b34187bdb841f2599770b7e6ff8e259dc3bb64fc7893acf44998acf5f8", + "sha256:232f0b52a5b978288f0bbc282a6c03fe48cd19a04202df44309919c142b3bb9c", + "sha256:24bcfa86fd9ce86b73a8368383c39d919c497a06eebb888b6f0c12f13e920b1a", + "sha256:25b8f60b5c7da71e64c18888f3067d5b6f1334b9681876b2fb41eea26de881ae", + "sha256:2714160a63da18aed9340c70ed514973971ee7e665e6b336917ff4cca81a25b1", + "sha256:2ca2cd5264e84b2cafc73f0045437f70c6378c0d7dbcddc9ee3fe192c1e29e5d", + "sha256:2cc707fc9aad2592fc686d63ef72dc0031fc98b6fb921d2f5395d9ab84fbc3ef", + "sha256:348630edea485f4228233c2f310a598abf8afa5f8c716c02a9698089687b6085", + "sha256:40fbfd6b044c9db13aeec1daf5887d322c710d811f944011757526ef6e323fd9", + "sha256:46c9c6a1d1190c0b75ec7c0f339088309952b82ae8d67a79ff1319eb4e749b96", + "sha256:591506e088901bdc25620c37aec885e82cc896528f28c57e113751e3471fc314", + "sha256:5ac71bba1e07eab403b082c4428f868c1c9e26a21041436b4905c4c3d4e49b08", + "sha256:5f622f19abda4e934938e24f1d67599249abc201844933a6f01aaa8663094489", + "sha256:65bead1ac8c8930cf92a1ccaedcce19a57298547d5d1db5c9d4d068a0675c38b", + "sha256:7362a7f829feda10c7265b553455de596b83d1623b3d436b6d3c51c688c57bf6", + "sha256:7f2675750c50151f806070ec11258edf4c328340916c53bac0adbc465abd6b1e", + "sha256:960d7f42277391e8b1c0b0ae427a214e1b31a1278de6b73f8807b20c2e913bba", + "sha256:a50b0888d8a021a3342d36a6086501e30de7d840ab68fca44913e97d14487dc1", + "sha256:b7dbc5e8c39ea3ad3db22715f1b5401cd698a621218680c6daf42c2f9d36e205", + "sha256:bb3d29df5d07d5399d58a394d0ef50adf303ab4fbf66dfd25b9ef258effcb692", + "sha256:c0fff2733f7c2950f58a4fd09b5db257b00c6fec57bf3f68c5bae004d804b407", + "sha256:c792d3707a86c01c02607ae74364854220fb3e82735f631cd0a345dea6b4cee5", + "sha256:c90bda74e16bcd03861b09b1d37c0a4158feda5d5a036bb2d6e58de6ff65793e", + "sha256:cfce79ce41cc1a1dc7fc85bb41eeeb32d34a4cf39a645c717c0550287e30ff06", + "sha256:eeafb646f374988c22c8e6da5ab9fb81367ecfe81c70c292623373d2a021b1a1", + "sha256:f425f50a6dd807cb9043d15a4fcfba3b5874a54d9587ccbb748899f70dc18c47", + "sha256:fcd4459fe35a400b8f416bc57906862693c9f88b66dc925e7f2a933e77f6b18b", + "sha256:ff3936dd5feaefb4f91c8c1f50a06c588b5dc69fba4f7d9c79a6617ad80bb7df" ], "index": "pypi", - "version": "==5.0" + "version": "==5.0.1" }, "decorator": { "hashes": [ @@ -481,24 +483,24 @@ }, "mypy": { "hashes": [ - "sha256:02d9bdd3398b636723ecb6c5cfe9773025a9ab7f34612c1cde5c7f2292e2d768", - "sha256:088f758a50af31cf8b42688118077292370c90c89232c783ba7979f39ea16646", - "sha256:28e9fbc96d13397a7ddb7fad7b14f373f91b5cff538e0772e77c270468df083c", - "sha256:30e123b24931f02c5d99307406658ac8f9cd6746f0d45a3dcac2fe5fbdd60939", - "sha256:3294821b5840d51a3cd7a2bb63b40fc3f901f6a3cfb3c6046570749c4c7ef279", - "sha256:41696a7d912ce16fdc7c141d87e8db5144d4be664a0c699a2b417d393994b0c2", - "sha256:4f42675fa278f3913340bb8c3371d191319704437758d7c4a8440346c293ecb2", - "sha256:54d205ccce6ed930a8a2ccf48404896d456e8b87812e491cb907a355b1a9c640", - "sha256:6992133c95a2847d309b4b0c899d7054adc60481df6f6b52bb7dee3d5fd157f7", - "sha256:6ecbd0e8e371333027abca0922b0c2c632a5b4739a0c61ffbd0733391e39144c", - "sha256:83fa87f556e60782c0fc3df1b37b7b4a840314ba1ac27f3e1a1e10cb37c89c17", - "sha256:c87ac7233c629f305602f563db07f5221950fe34fe30af072ac838fa85395f78", - "sha256:de9ec8dba773b78c49e7bec9a35c9b6fc5235682ad1fc2105752ae7c22f4b931", - "sha256:f385a0accf353ca1bca4bbf473b9d83ed18d923fdb809d3a70a385da23e25b6a" + "sha256:0a9a45157e532da06fe56adcfef8a74629566b607fa2c1ac0122d1ff995c748a", + "sha256:2c35cae79ceb20d47facfad51f952df16c2ae9f45db6cb38405a3da1cf8fc0a7", + "sha256:4b9365ade157794cef9685791032521233729cb00ce76b0ddc78749abea463d2", + "sha256:53ea810ae3f83f9c9b452582261ea859828a9ed666f2e1ca840300b69322c474", + "sha256:634aef60b4ff0f650d3e59d4374626ca6153fcaff96ec075b215b568e6ee3cb0", + "sha256:7e396ce53cacd5596ff6d191b47ab0ea18f8e0ec04e15d69728d530e86d4c217", + "sha256:7eadc91af8270455e0d73565b8964da1642fe226665dd5c9560067cd64d56749", + "sha256:7f672d02fffcbace4db2b05369142e0506cdcde20cea0e07c7c2171c4fd11dd6", + "sha256:85baab8d74ec601e86134afe2bcccd87820f79d2f8d5798c889507d1088287bf", + "sha256:87c556fb85d709dacd4b4cb6167eecc5bbb4f0a9864b69136a0d4640fdc76a36", + "sha256:a6bd44efee4dc8c3324c13785a9dc3519b3ee3a92cada42d2b57762b7053b49b", + "sha256:c6d27bd20c3ba60d5b02f20bd28e20091d6286a699174dfad515636cb09b5a72", + "sha256:e2bb577d10d09a2d8822a042a23b8d62bc3b269667c9eb8e60a6edfa000211b1", + "sha256:f97a605d7c8bc2c6d1172c2f0d5a65b24142e11a58de689046e62c2d632ca8c1" ], "index": "pypi", "markers": "python_version >= '3.5'", - "version": "==0.750" + "version": "==0.761" }, "mypy-extensions": { "hashes": [ @@ -614,11 +616,11 @@ }, "sphinx": { "hashes": [ - "sha256:3b16e48e791a322d584489ab28d8800652123d1fbfdd173e2965a31d40bf22d7", - "sha256:559c1a8ed1365a982f77650720b41114414139a635692a23c2990824d0a84cf2" + "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159", + "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5" ], "index": "pypi", - "version": "==2.2.2" + "version": "==2.3.1" }, "sphinxcontrib-applehelp": { "hashes": [ From 3aeaaf2ec38870a16a54c96dd6855104779f2e9c Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 22:30:43 -0800 Subject: [PATCH 079/228] Fix second filtergen test to make sure it raises the correct ValueError --- test/test_modulator_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index ed5f1b1..dcde3b3 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -21,7 +21,7 @@ def test_filtergen_error(): with pytest.raises(ValueError): filt=lowpass_fir_filter(2000,500,100) with pytest.raises(ValueError): - filt=lowpass_fir_filter(2000,5000,1000) + filt=lowpass_fir_filter(2000,1000,4000) @pytest.mark.unit def test_unit_average_int(): From 1040d1fae388e8bf60ca10f344df08d3b1c4a151 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 22 Dec 2019 23:47:51 -0800 Subject: [PATCH 080/228] Final revisions to Gaussian smoothing notebook --- .../Pre-Modulation Gaussian Smoothing.ipynb | 107 ++++++++++++++---- 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb b/docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb index c2e858a..65b1f2c 100644 --- a/docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb +++ b/docs/notebooks/Pre-Modulation Gaussian Smoothing.ipynb @@ -41,7 +41,7 @@ "The typical window size for a Gaussian kernel is $6\\sigma+1$ for integer $\\sigma$, so we generalize this for all positive $\\sigma$ as shown below (making sure that $w$ remains odd to avoid scalloping):\n", "\n", "$$\n", - "w=\n", + "n=\n", "\\begin{cases}\n", "\\lceil 6\\sigma+1 \\rceil & \\lceil 6\\sigma+1 \\rceil \\equiv 1 \\bmod 2 \\\\\n", "\\lceil 6\\sigma+1 \\rceil + 1 & \\lceil 6\\sigma+1 \\rceil \\equiv 0 \\bmod 2\n", @@ -72,7 +72,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "There are two criteria that we use to calculate the sigma and prevent intersymbol interference: the time-domain criterion and the frequency-domain criterion." + "There are two criteria that we use to calculate a sigma that has sufficient smoothing and avoids intersymbol interference: the time-domain criterion and the frequency-domain criterion." ] }, { @@ -88,7 +88,7 @@ "source": [ "Given a pulse with a width, we want to ensure that the total portion of the transition significantly affected by smoothing is at most half the pulse width.\n", "\n", - "To more precisely define \"significantly affected\", we define a threshold such that, for a transition from extremes, the filtered pulse settles on the final value close enough to be unambiguously different from the neighboring symbols. We assume 256 distinct, evenly spaced symbols, leaving a band of $s\\pm\\frac{1}{512}$ in which a symbol can be recognized unambiguously." + "To more precisely define \"significantly affected\", we define a threshold such that, for a transition from extremes, the filtered pulse settles on the final value $s$ to within a band $s\\pm\\Delta s$ to be unambiguously different from the neighboring symbols. For 256 distinct, evenly spaced symbols, we have $\\Delta s = \\frac{1}{512}$." ] }, { @@ -99,7 +99,7 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", @@ -111,30 +111,79 @@ "\n", "\n", "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", - " \n", - "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - " \n", + " \n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - " \n", + " \n", "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", "\n", - "\n", - "\n", - "\n", - "\n", "\n", "" ], @@ -163,13 +212,21 @@ "\\begin{tikzpicture}\n", "\n", "\\draw[thick,<->] (-5,0) -- (5,0) coordinate [label={below: $t$}] (xmax);\n", - "\\draw[thick,->] (0,0) -- (0,4) coordinate [label={right: $y$}] (ymax);\n", + "\\draw[thick,->] (0,0) -- (0,5) coordinate [label={above: $y$}] (ymax);\n", "\n", "\\draw[blue,domain=-5:5,smooth,variable=\\x] plot ({\\x},{4*(normcdf(\\x+6,0,\\sigmaval)-normcdf(\\x-6,0,\\sigmaval))});\n", - "\\draw[red] ({-\\vertlineval},0) -- ({-\\vertlineval},4);\n", - "\\draw[red] ({\\vertlineval},0) -- ({\\vertlineval},4);\n", - "\\draw[red] (-5,4/16) -- (5,4/16);\n", + "\\draw[green,thick,dashed] (-5,4) -- (5,4);\n", + "\n", + "\\draw[red] ({-\\vertlineval},0) -- ({-\\vertlineval},5);\n", + "\\draw[red] ({\\vertlineval},0) -- ({\\vertlineval},5);\n", "\\draw[red] (-5,4-4/16) -- (5,4-4/16);\n", + "\\draw[red] (-5,4+4/16) -- (5,4+4/16);\n", + "\n", + "\\draw[<->] (-5,1) -- (-1,1) coordinate [label={above: $w$}] (totalpoint) -- (5,1);\n", + "\\draw[<->] ({-\\vertlineval},0.5) -- (-1,0.5) coordinate [label={above: $l$}] (pulsepoint) -- ({\\vertlineval},0.5);\n", + "\\draw[->] (3.5,0) -- (3.5, 2) coordinate [label={right:$s$}] -- (3.5,4);\n", + "\\draw[<->] (4,4-4/16) -- (4,4) coordinate [label={right: $s\\pm\\Delta s$}] (band) -- (4,4+4/16);\n", + "\n", "\\end{tikzpicture}\n", "\\end{document}" ] @@ -178,7 +235,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The usable segment $l=\\frac{w}{2}$ of the pulse is $w-2k_t \\sigma_t$. Solving for $\\sigma_t$ gives\n", + "The usable segment $l=\\frac{w}{2}$ of the pulse is $l=w-2k_t \\sigma_t$. Solving for $\\sigma_t$ gives\n", "\n", "\\begin{align*}\n", "\\frac{w}{2}&=w-2k_t \\sigma_t \\\\\n", From 226f35fc0e36e796ab7637d6710b9a068ef2572b Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 23 Dec 2019 17:50:10 -0800 Subject: [PATCH 081/228] Remove Trapezoidal Averaging legacy notebook --- docs/notebooks/Trapezoidal Averaging.ipynb | 905 --------------------- 1 file changed, 905 deletions(-) delete mode 100644 docs/notebooks/Trapezoidal Averaging.ipynb diff --git a/docs/notebooks/Trapezoidal Averaging.ipynb b/docs/notebooks/Trapezoidal Averaging.ipynb deleted file mode 100644 index 07f61f1..0000000 --- a/docs/notebooks/Trapezoidal Averaging.ipynb +++ /dev/null @@ -1,905 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Derivation of Trapezoidal Averaging Code" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**NOTE: This is now obsolete since the new function uses `numpy.trapz`. This notebook will be removed at a later date.**\n", - "\n", - "This notebook explains how the averaging function uses linear interpolation and trapezoidal integration. Recall that the average in terms of integration is given by\n", - "\n", - "$$\n", - "\\frac{1}{x_2-x_1} \\int_{x_1}^{x_2} f(t) dt\n", - "$$\n", - "\n", - "and that linear interpolation for a fraction $0 \\le \\alpha \\le 1$ between two values $y_1$ and $y_2$ is given by $(1-\\alpha)y_1+\\alpha y_2$.\n", - "\n", - "We will perform trapezoidal integration with data points $y[n]=f(n)$ for integer $n$ while allowing the bounds $x_1$ and $x_2$ to be any real number.\n", - "\n", - "*Note: there appear to be bugs in the SVG renderer of the Jupyter web interface, so subscripts in labels for the graphs may not match those in the text.*" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext itikz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will denote the $x$ coordinate of the beginning point by $x_1$, and the $x$ coordinate of the end point by $x_2$. We attach the letters $i$ and $f$ to the subscript to denote the integer and fractional components of $x_1$ and $x_2$, defined as follows:\n", - "\n", - "$$\n", - "x_{1i}=\\lfloor x_1 \\rfloor \\\\\n", - "x_{1f}=x_1-\\lfloor x_1 \\rfloor \\\\\n", - "x_{2i}=\\lceil x_2 \\rceil -1 \\\\\n", - "x_{2f}=1-\\left(\\lceil x_2 \\rceil - x_2\\right)\n", - "$$\n", - "\n", - "Note that $(0 \\le x_{1f} < 1)$ and $(0 < x_{2f} \\le 1)$.\n", - "\n", - "There are three possible cases that can occur when performing the integration:\n", - "\n", - " - Case 1: The endpoints are in the same trapezoid\n", - " - Case 2: The endpoints are in adjacent trapezoids\n", - " - Case 3: The endpoints are separated from each other" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Case 1: Endpoints in Same Trapezoid" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%%itikz --temp-dir\n", - "\\documentclass[tikz]{standalone}\n", - "\\usetikzlibrary{intersections}\n", - "\\begin{document}\n", - "\\def\\xbegin{2.75}\n", - "\\def\\xend{3.75}\n", - "\\begin{tikzpicture}\n", - " % Axes\n", - " \\draw[thick, ->] (-0.5,0) -- (10,0) coordinate[label = {below:$x$}] (xmax);\n", - " \\draw[thick, ->] (0,-0.5) -- (0,6) coordinate[label = {right:$f(x)$}] (ymax);\n", - " \\foreach \\x in {0,2,...,10}\n", - " \\node (\\x point) at (\\x,0) {\\textbullet};\n", - "\n", - " % Curve itself\n", - " \\draw[blue, thick, name path=curve] plot[smooth] coordinates {(-0.5,1) (3,4) (6,2) (10,5)};\n", - " \n", - " % Trapezoid stuff\n", - " \\coordinate [label=below:$x_1$] (xleft) at (\\xbegin,0) {};\n", - " \\coordinate [label=below:$x_2$] (xright) at (\\xend,0) {};\n", - " \\node (topleft high) at (\\xbegin,6) {};\n", - " \\node (topright high) at (\\xend,6) {};\n", - " \\path [name path=trapezoid left line] (xleft)--(topleft high);\n", - " \\path [name path=trapezoid right line] (xright)--(topright high);\n", - " \\path [name intersections={of=trapezoid left line and curve, by=trapezoidleft}];\n", - " \\path [name intersections={of=trapezoid right line and curve, by=trapezoidright}];\n", - " \\draw (xleft) -- (trapezoidleft) -- (trapezoidright) -- (xright) -- cycle;\n", - "\n", - " % Fractional component arrows\n", - " \\draw (2,0) -- (2,2);\n", - " \\draw [->] (2,1.5) -- (\\xbegin,1.5) node[midway, label=above:$x_{1f}$] {};\n", - " \\draw [->] (2,0.5) -- (\\xend,0.5) node[near end, label=above:$x_{2f}$] {};\n", - "\\end{tikzpicture}\n", - "\\end{document}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since $x_{1i}=x_{2i}$, we only have a single trapezoid with width $x_{2f}-x_{1f}$. The heights are the interpolated values at $x_1$ and $x_2$:\n", - "\n", - "$$\n", - "f(x_1)=(1-x_{1f})y[x_{1i}]+x_{1f}y[x_{1i}+1]\n", - "$$\n", - "\n", - "and likewise\n", - "\n", - "$$\n", - "f(x_2)=(1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]\n", - "$$\n", - "\n", - "The area of the trapezoid is\n", - "\n", - "$$\n", - "\\frac{1}{2}\\left(x_{2f}-x_{1f}\\right)\\left((1-x_{1f})y[x_1]+ \\\\\n", - "x_{1f}y[x_1+1]+(1-x_{2f})y[x_2]+x_{2f}y[x_2+1]\\right)\n", - "$$\n", - "\n", - "so the average is\n", - "\n", - "$$\n", - "\\frac{1}{2\\left(x_{2f}-x_{1f}\\right)} \\left(x_{2f}-x_{1f}\\right)\\left((1-x_{1f})y[x_1]+x_{1f}y[x_1+1]\\\\\n", - "+(1-x_{2f})y[x_2]+x_{2f}y[x_2+1]\\right) \\\\\n", - "=\\frac{1}{2}\\left((1-x_{1f})y[x_1]+x_{1f}y[x_1+1]+(1-x_{2f})y[x_2]+x_{2f}y[x_2+1]\\right)\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Case 2: Endpoints in Adjacent Trapezoids" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%%itikz --temp-dir\n", - "\\documentclass[tikz]{standalone}\n", - "\\usetikzlibrary{intersections}\n", - "\\begin{document}\n", - "\\def\\xbegin{2.75}\n", - "\\def\\xmid{4}\n", - "\\def\\xend{5.5}\n", - "\\begin{tikzpicture}\n", - " % Axes\n", - " \\draw[thick, ->] (-0.5,0) -- (10,0) coordinate[label = {below:$x$}] (xmax);\n", - " \\draw[thick, ->] (0,-0.5) -- (0,6) coordinate[label = {right:$f(x)$}] (ymax);\n", - " \\foreach \\x in {0,2,...,10}\n", - " \\node (\\x point) at (\\x,0) {\\textbullet};\n", - "\n", - " % Curve itself\n", - " \\draw[blue, thick, name path=curve] plot[smooth] coordinates {(-0.5,1) (3,4) (6,2) (10,5)};\n", - " \n", - " % Trapezoid stuff\n", - " \\coordinate [label=below:$x_1$] (xleft) at (\\xbegin,0) {};\n", - " \\coordinate [label=below:$x_{2i}$] (xmid) at (\\xmid,0) {};\n", - " \\coordinate [label=below:$x_2$] (xright) at (\\xend,0) {};\n", - " \\node (topleft high) at (\\xbegin,6) {};\n", - " \\node (topmid high) at (\\xmid, 6) {};\n", - " \\node (topright high) at (\\xend,6) {};\n", - " \\path [name path=trapezoid left line] (xleft)--(topleft high);\n", - " \\path [name path=trapezoid mid line] (xmid) -- (topmid high);\n", - " \\path [name path=trapezoid right line] (xright)--(topright high);\n", - " \\path [name intersections={of=trapezoid left line and curve, by=trapezoidleft}];\n", - " \\path [name intersections={of=trapezoid mid line and curve, by=trapezoidmid}];\n", - " \\path [name intersections={of=trapezoid right line and curve, by=trapezoidright}];\n", - " \\draw (xleft) -- (trapezoidleft) -- (trapezoidmid) -- (xmid) -- cycle;\n", - " \\draw (xmid) -- (trapezoidmid) -- (trapezoidright) -- (xright) -- cycle;\n", - "\n", - " % Fractional component arrows\n", - " \\draw (2,0) -- (2,2);\n", - " \\draw (4,0) -- (4,1);\n", - " \\draw [->] (2,1.5) -- (\\xbegin,1.5) node[midway, label=above:$x_{1f}$] {};\n", - " \\draw [->] (4,0.5) -- (\\xend,0.5) node[near end, label=above:$x_{2f}$] {};\n", - "\\end{tikzpicture}\n", - "\\end{document}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since $x_{1i}=x_{2i}$, we have two trapezoids with width $1-x_{1f}+x_{2f}$. The heights are the interpolated values at $x_1$ and $x_2$:\n", - "\n", - "$$\n", - "f(x_1)=(1-x_{1f})y[x_{1i}]+x_{1f}y[x_{1i}+1]\n", - "$$\n", - "\n", - "and likewise\n", - "\n", - "$$\n", - "f(x_2)=(1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]\n", - "$$\n", - "\n", - "The area of the two trapezoids together is\n", - "\n", - "$$\n", - "\\frac{1}{2} \\left(1-x_{1f}\\right) \\left((1-x_{1f})y[x_1]+x_{1f}y[x_{2i}]+y[x_{2i}]\\right) \\\\\n", - "+ \\frac{1}{2} x_{2f} \\left((1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]+y[x_{2i}]\\right)\n", - "$$\n", - "\n", - "so the average is\n", - "\n", - "$$\n", - "\\frac{1}{2\\left(1-x_{1f}+x_{2f}\\right)}\\left( \\left(1-x_{1f}\\right) \\left((1-x_{1f})y[x_1]+x_{1f}y[x_{2i}]+y[x_{2i}]\\right) \\\\\n", - "+ x_{2f} \\left((1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]+y[x_{2i}]\\right) \\right)\n", - "$$\n", - "\n", - "which simplifies to\n", - "\n", - "$$\n", - "\\frac{1}{2\\left(1-x_{1f}+x_{2f}\\right)} \\left( \\left((1-x_{1f})^2y[x_1]+x_{1f}(1-x_{1f})y[x_{2i}]\\right) \\\\\n", - "+\\left(x_{2f}(1-x_{2f})y[x_{2i}]+x_{2f}^2y[x_{2i}+1]\\right)+2 y[x_{2i}]\\right)\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Case 3: Separated Endpoints" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - "" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "%%itikz --temp-dir\n", - "\\documentclass[tikz]{standalone}\n", - "\\usetikzlibrary{intersections}\n", - "\\begin{document}\n", - "\\def\\xbegin{2.75}\n", - "\\def\\xmidl{4}\n", - "\\def\\xmidr{6}\n", - "\\def\\xend{7}\n", - "\\begin{tikzpicture}\n", - " % Axes\n", - " \\draw[thick, ->] (-0.5,0) -- (10,0) coordinate[label = {below:$x$}] (xmax);\n", - " \\draw[thick, ->] (0,-0.5) -- (0,6) coordinate[label = {right:$f(x)$}] (ymax);\n", - " \\foreach \\x in {0,2,...,10}\n", - " \\node (\\x point) at (\\x,0) {\\textbullet};\n", - "\n", - " % Curve itself\n", - " \\draw[blue, thick, name path=curve] plot[smooth] coordinates {(-0.5,1) (3,4) (6,2) (10,5)};\n", - " \n", - " % Trapezoid stuff\n", - " \\coordinate [label=below:$x_1$] (xleft) at (\\xbegin,0) {};\n", - " \\coordinate [label=below:$x_i$] (xmidl) at (\\xmidl,0) {};\n", - " \\coordinate [label=below:$x_{2i}$] (xmidr) at (\\xmidr,0) {};\n", - " \\coordinate [label=below:$x_2$] (xright) at (\\xend,0) {};\n", - " \\node (topleft high) at (\\xbegin,6) {};\n", - " \\node (topmidl high) at (\\xmidl, 6) {};\n", - " \\node (topmidr high) at (\\xmidr, 6) {};\n", - " \\node (topright high) at (\\xend,6) {};\n", - " \\path [name path=trapezoid left line] (xleft)--(topleft high);\n", - " \\path [name path=trapezoid midl line] (xmidl) -- (topmidl high);\n", - " \\path [name path=trapezoid midr line] (xmidr) -- (topmidr high);\n", - " \\path [name path=trapezoid right line] (xright)--(topright high);\n", - " \\path [name intersections={of=trapezoid left line and curve, by=trapezoidleft}];\n", - " \\path [name intersections={of=trapezoid midl line and curve, by=trapezoidmidl}];\n", - " \\path [name intersections={of=trapezoid midr line and curve, by=trapezoidmidr}];\n", - " \\path [name intersections={of=trapezoid right line and curve, by=trapezoidright}];\n", - " \\draw (xleft) -- (trapezoidleft) -- (trapezoidmidl) -- (xmidl) -- cycle;\n", - " \\draw (xmidl) -- (trapezoidmidl) -- (trapezoidmidr) -- (xmidr) -- cycle;\n", - " \\draw (xmidr) -- (trapezoidmidr) -- (trapezoidright) -- (xright) -- cycle;\n", - "\n", - " % Fractional component arrows\n", - " \\draw (2,0) -- (2,2);\n", - " \\draw (6,0) -- (6,1);\n", - " \\draw [->] (2,1.5) -- (\\xbegin,1.5) node[midway, label=above:$x_{1f}$] {};\n", - " \\draw [->] (6,0.5) -- (\\xend,0.5) node[midway, label=above:$x_{2f}$] {};\n", - "\\end{tikzpicture}\n", - "\\end{document}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can apply normal trapezoidal integration to the center trapezoids, leaving the special handling only for the trapezoids at the beginning and end. We previously derived formulas for the end trapezoids, so adding all the trapezoids together produces\n", - "\n", - "$$\n", - "\\frac{1}{2} \\left(1-x_{1f}\\right) \\left((1-x_{1f})y[x_1]+x_{1f}y[x_1+1]+y[x_1+1]\\right) \\\\\n", - "+ \\frac{1}{2} \\left(y[x_1+1]+y[x_1+2]\\right) + \\cdots + \\frac{1}{2} \\left(y[x_{2i}-1]+y[x_{2i}]\\right) \\\\\n", - "\\frac{1}{2} x_{2f} \\left((1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]+y[x_{2i}]\\right)\n", - "$$\n", - "\n", - "The intermediate terms combine in the usual manner, leaving us with\n", - "\n", - "$$\n", - "\\frac{1}{2} \\left(1-x_{1f}\\right) \\left((1-x_{1f})y[x_1]+x_{1f}y[x_1+1]\\right) \\\\\n", - "+ \\sum_{n=x_1+1}^{x_{2i}} y[n] + \\frac{1}{2} x_{2f} \\left((1-x_{2f})y[x_{2i}]+x_{2f}y[x_{2i}+1]\\right)\n", - "$$\n", - "\n", - "or\n", - "\n", - "$$\n", - "\\frac{1}{2} \\left((1-x_{1f})^2y[x_1]+x_{1f}(1-x_{1f})y[x_{2i}]\\right) \\\\\n", - "+ \\sum_{n=x_1+1}^{x_{2i}} y[n] + \\frac{1}{2} x_{2f} \\left(x_{2f}(1-x_{2f})y[x_{2i}]+x_{2f}^2y[x_{2i}+1]\\right)\n", - "$$\n", - "\n", - "as the total area under the trapezoids. The average is therefore\n", - "\n", - "$$\n", - "\\frac{1}{x_2-x_1} \\left(\n", - "\\frac{1}{2} \\left((1-x_{1f})^2y[x_1]+x_{1f}(1-x_{1f})y[x_{2i}]\\right) \\\\\n", - "+ \\sum_{n=x_1+1}^{x_{2i}} y[n] + \\frac{1}{2} x_{2f} \\left(x_{2f}(1-x_{2f})y[x_{2i}]+x_{2f}^2y[x_{2i}+1]\\right) \\right)\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Final code" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is the final code that combines the three cases to produce an averaging routine using trapezoidal integration.\n", - "\n", - "*This is not synced to the actual code in the repo, but this should theoretically never need to be updated again.*" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "\"\"\"\n", - "Computes the average of the data over the specified interval with integrals.\n", - "\n", - "The bounds on the given interval need not be integers.\n", - "Linear interpolation is used for noninteger bounds.\n", - "\n", - "Note: Due to trapezoidal approximation this will not produce the\n", - "normal average if the bounds are integers.\n", - "Both endpoints are explicitly included, unlike normal array slicing.\n", - "In addition, the values at the extremities receive half the weight\n", - "as the rest of the data, following the trapezoidal integration formula.\n", - "\"\"\"\n", - "def average_interval_data(data, begin, end):\n", - " if end(len(data)-1):\n", - " raise ValueError(\"Invalid index range specified\")\n", - " width=end-begin\n", - " # Handle special case of width=0\n", - " if width==0:\n", - " index_int=int(np.floor(begin))\n", - " index_frac=begin-np.floor(begin)\n", - " return (1-index_frac)*data[index_int]+index_frac*data[index_int+1]\n", - "\n", - " # Get bounding indicies\n", - " begin_index=int(np.floor(begin))\n", - " begin_frac=begin-begin_index\n", - " end_index=int(np.ceil(end))-1\n", - " end_frac=1-(np.ceil(end)-end)\n", - "\n", - " if end_index-begin_index>0:\n", - " if end_index-begin_index==1:\n", - " # Case 2: two trapezoids with no whole trapezoids in between\n", - " # Compute the portion of the trapzeoid normally folded into sum\n", - " # Lump sum does not work because the widths are smaller\n", - " sum_interval=data[end_index]*(1-begin_frac+end_frac)\n", - " sum_interval*=0.5\n", - " else:\n", - " # Case 3: general trapezoidal integration\n", - " # Increment begin_index to exclude start element\n", - " # Increment end_index to include second-to-last element\n", - " # but exclude last\n", - " sum_interval=sum(data[begin_index+1:end_index+1])\n", - "\n", - " # Compute beginning contribution\n", - " begin_val=(1-begin_frac)**2 * data[begin_index] \\\n", - " + begin_frac*(1-begin_frac)*data[begin_index+1]\n", - " begin_val*=0.5\n", - " # Compute ending contribution\n", - " end_val=end_frac*(1-end_frac)*data[end_index] \\\n", - " + end_frac**2*data[end_index+1]\n", - " end_val*=0.5\n", - "\n", - " # Add this to the sum and average by dividing out width\n", - " sum_interval+=(begin_val+end_val)\n", - " else:\n", - " # Case 1: A single trapezoid\n", - " # begin_index and end_index equal here, but separate for readability\n", - " sum_interval=(end_frac-begin_frac)* \\\n", - " ((1-begin_frac)*data[begin_index]+begin_frac*data[begin_index+1]+ \\\n", - " (1-end_frac)*data[end_index]+end_frac*data[end_index+1])\n", - " sum_interval*=0.5\n", - " return sum_interval/width" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} \ No newline at end of file From 178a9aa9976a041f6ee5e0066ce56fabd137f593 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 23 Dec 2019 18:13:37 -0800 Subject: [PATCH 082/228] Move type annotations for encoders into actual .py files --- voicechat_modem_dsp/encoders/bitstream.py | 12 +++++-- voicechat_modem_dsp/encoders/bitstream.pyi | 11 ------- .../encoders/ecc/hamming_7_4.py | 5 +-- .../encoders/ecc/hamming_7_4.pyi | 9 ----- voicechat_modem_dsp/encoders/encode_pad.py | 33 +++++++++++-------- voicechat_modem_dsp/encoders/encode_pad.pyi | 24 -------------- 6 files changed, 31 insertions(+), 63 deletions(-) delete mode 100644 voicechat_modem_dsp/encoders/bitstream.pyi delete mode 100644 voicechat_modem_dsp/encoders/ecc/hamming_7_4.pyi delete mode 100644 voicechat_modem_dsp/encoders/encode_pad.pyi diff --git a/voicechat_modem_dsp/encoders/bitstream.py b/voicechat_modem_dsp/encoders/bitstream.py index a482cbb..412a858 100644 --- a/voicechat_modem_dsp/encoders/bitstream.py +++ b/voicechat_modem_dsp/encoders/bitstream.py @@ -1,18 +1,24 @@ +from typing import Union, Iterator + +readable_bytearr=Union[bytes, bytearray] +writeable_bytearr=Union[bytearray] + # Bitstream functions are MSB first -def read_bitstream(bitstream,position): +def read_bitstream(bitstream: readable_bytearr, position: int) -> bool: if position<0 or position>=8*len(bitstream): raise ValueError("Position index is out of range") byteindex=position//8 shift_val=7-position%8 return bool((bitstream[byteindex] & (1<> shift_val) -def read_bitstream_iterator(bitstream): +def read_bitstream_iterator(bitstream: readable_bytearr) -> Iterator[bool]: for byte in bitstream: for shift_val in range(7,-1,-1): yield bool((byte & (1<> shift_val) -def write_bitstream(bitstream, position, bit): +def write_bitstream(bitstream: writeable_bytearr, + position: int, bit: bool) -> None: # Modifies bitstream in place so raise TypeError if wrong type if isinstance(bitstream, bytes): raise TypeError("Bitstream should be mutable") diff --git a/voicechat_modem_dsp/encoders/bitstream.pyi b/voicechat_modem_dsp/encoders/bitstream.pyi deleted file mode 100644 index 2c9f3a7..0000000 --- a/voicechat_modem_dsp/encoders/bitstream.pyi +++ /dev/null @@ -1,11 +0,0 @@ -# Type stub for encoders.bitstream (Python 3) - -from typing import Union, Iterator - -readable_bytearr=Union[bytes, bytearray] -writeable_bytearr=Union[bytearray] - -def read_bitstream(bitstream: readable_bytearr, position: int) -> bool: ... -def read_bitstream_iterator(bitstream: readable_bytearr) -> Iterator[bool]: ... -def write_bitstream(bitstream: writeable_bytearr, - position: int, bit: bool) -> None: ... diff --git a/voicechat_modem_dsp/encoders/ecc/hamming_7_4.py b/voicechat_modem_dsp/encoders/ecc/hamming_7_4.py index 5f266f7..7c1c18b 100644 --- a/voicechat_modem_dsp/encoders/ecc/hamming_7_4.py +++ b/voicechat_modem_dsp/encoders/ecc/hamming_7_4.py @@ -1,11 +1,12 @@ import math from ..bitstream import read_bitstream, write_bitstream +from ..bitstream import readable_bytearr # Functions should accept either bytes or bytearrays # Manipulate bytearrays during construction but return bytes # Once encoded, data should be immutable -def hamming_encode_7_4(bitstream): +def hamming_encode_7_4(bitstream: readable_bytearr) -> bytes: output_arr=bytearray(math.ceil(len(bitstream)*7/4)) output_index=0 for i in range(0,8*len(bitstream),4): @@ -34,7 +35,7 @@ def hamming_encode_7_4(bitstream): # Functions should accept either bytes or bytearrays # Manipulate bytearrays during construction but return bytes # Once encoded, data should be immutable -def hamming_decode_7_4(bitstream): +def hamming_decode_7_4(bitstream: readable_bytearr) -> bytes: output_arr=bytearray(math.floor(len(bitstream)*4/7)) output_index=0 for i in range(0,8*len(bitstream),7): diff --git a/voicechat_modem_dsp/encoders/ecc/hamming_7_4.pyi b/voicechat_modem_dsp/encoders/ecc/hamming_7_4.pyi deleted file mode 100644 index a3c7980..0000000 --- a/voicechat_modem_dsp/encoders/ecc/hamming_7_4.pyi +++ /dev/null @@ -1,9 +0,0 @@ -# Stubs for encoders.ecc.hamming_7_4 (Python 3) -# -# NOTE: This dynamically typed stub was automatically generated by stubgen. - -from ..bitstream import readable_bytearr, read_bitstream, write_bitstream -from typing import Any - -def hamming_encode_7_4(bitstream: readable_bytearr) -> bytes: ... -def hamming_decode_7_4(bitstream: readable_bytearr) -> bytes: ... diff --git a/voicechat_modem_dsp/encoders/encode_pad.py b/voicechat_modem_dsp/encoders/encode_pad.py index dc48b47..466964d 100644 --- a/voicechat_modem_dsp/encoders/encode_pad.py +++ b/voicechat_modem_dsp/encoders/encode_pad.py @@ -1,15 +1,18 @@ from .bitstream import read_bitstream_iterator, write_bitstream +from .bitstream import readable_bytearr, writeable_bytearr + +from typing import List, Dict, Callable import base64 # These return a list of numbers corresponding to symbols -def base_2_encode(bitstream): +def base_2_encode(bitstream: readable_bytearr) -> List[int]: return_list=list() for bit in read_bitstream_iterator(bitstream): return_list.append(1 if bit else 0) return return_list -def base_2_decode(datastream): +def base_2_decode(datastream: List[int]) -> readable_bytearr: base_2_mapping={0:False, 1:True} if len(datastream)%8 != 0: raise ValueError("Inappropriate datastream length") @@ -21,7 +24,7 @@ def base_2_decode(datastream): raise ValueError("Illegal symbol detected in datastream") return bytes(return_bytearray) -def base_4_encode(bitstream): +def base_4_encode(bitstream: readable_bytearr) -> List[int]: return_list=list() rollover_counter=0 emit_symbol=False @@ -35,7 +38,7 @@ def base_4_encode(bitstream): emit_symbol=not emit_symbol return return_list -def base_4_decode(datastream): +def base_4_decode(datastream: List[int]) -> readable_bytearr: base_4_mapping={0:(False,False), 1:(False,True), 2:(True,False), @@ -52,7 +55,7 @@ def base_4_decode(datastream): raise ValueError("Illegal symbol detected in datastream") return bytes(return_bytearray) -def base_8_encode(bitstream): +def base_8_encode(bitstream: readable_bytearr) -> List[int]: return_list=list() rollover_counter=0 emit_symbol_counter=0 @@ -74,7 +77,7 @@ def base_8_encode(bitstream): return_list.append(rollover_counter) return return_list -def base_8_decode(datastream): +def base_8_decode(datastream: List[int]) -> readable_bytearr: base_8_mapping={0:(False,False,False), 1:(False,False,True), 2:(False,True,False), @@ -104,10 +107,10 @@ def base_8_decode(datastream): del return_bytearray[-1] return bytes(return_bytearray) -def base_16_encode(bitstream): +def base_16_encode(bitstream: readable_bytearr) -> List[int]: return [int(chr(c), 16) for c in base64.b16encode(bitstream)] -def base_16_decode(datastream): +def base_16_decode(datastream: List[int]) -> readable_bytearr: if len(datastream)%2!=0: raise ValueError("Inappropriate datastream length") for c in datastream: @@ -117,7 +120,7 @@ def base_16_decode(datastream): return base64.b16decode(bytes("".join([hex(c)[-1] for c in datastream]), "ascii"),casefold=True) -def base_32_encode(bitstream): +def base_32_encode(bitstream: readable_bytearr) -> List[int]: base_32_mapping = {char:int_val for int_val,char in enumerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")} base_32_encodestr=str(base64.b32encode(bitstream),"ascii") @@ -126,7 +129,7 @@ def base_32_encode(bitstream): # b32encode works properly -> KeyError impossible return [base_32_mapping[char] for char in base_32_encodestr] -def base_32_decode(datastream): +def base_32_decode(datastream: List[int]) -> readable_bytearr: # Convert to dict to avoid negative index wrapping base_32_mapping=dict(enumerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")) try: @@ -141,7 +144,7 @@ def base_32_decode(datastream): except ValueError: raise ValueError("Inappropriate datastream length") -def base_64_encode(bitstream): +def base_64_encode(bitstream: readable_bytearr) -> List[int]: base_64_mapping = {char:int_val for int_val,char in enumerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz0123456789+/")} @@ -151,7 +154,7 @@ def base_64_encode(bitstream): # b32encode works properly -> KeyError impossible return [base_64_mapping[char] for char in base_64_encodestr] -def base_64_decode(datastream): +def base_64_decode(datastream: List[int]) -> readable_bytearr: # Convert to dict to avoid negative index wrapping base_64_mapping=dict(enumerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz0123456789+/")) @@ -166,10 +169,10 @@ def base_64_decode(datastream): return base64.b64decode(base_64_str) # Bitstream already base 256 so conversion is a no-op -def base_256_encode(bitstream): +def base_256_encode(bitstream: readable_bytearr) -> List[int]: return list(bitstream) -def base_256_decode(datastream): +def base_256_decode(datastream: List[int]) -> readable_bytearr: # Catch ValueError to provide our own message here try: return bytes(datastream) @@ -177,6 +180,8 @@ def base_256_decode(datastream): raise ValueError("Illegal symbol detected in datastream") # Convenience mapping to allow for lookup based on len(modulation_list) +encode_function_mappings: Dict[int,Callable[[readable_bytearr], List[int]]] +decode_function_mappings: Dict[int,Callable[[List[int]], readable_bytearr]] encode_function_mappings = {2:base_2_encode, 4:base_4_encode, 8:base_8_encode, 16:base_16_encode, 32:base_32_encode, 64:base_64_encode, diff --git a/voicechat_modem_dsp/encoders/encode_pad.pyi b/voicechat_modem_dsp/encoders/encode_pad.pyi deleted file mode 100644 index c9d2dea..0000000 --- a/voicechat_modem_dsp/encoders/encode_pad.pyi +++ /dev/null @@ -1,24 +0,0 @@ -# Type stub for encoders.encode_pad (Python 3) - -from .bitstream import read_bitstream_iterator, readable_bytearr, writeable_bytearr, write_bitstream -from typing import Any, List, Dict, Callable - -def base_2_encode(bitstream: readable_bytearr) -> List[float]: ... -def base_2_decode(datastream: List[float]) -> readable_bytearr: ... -def base_4_encode(bitstream: readable_bytearr) -> List[float]: ... -def base_4_decode(datastream: List[float]) -> readable_bytearr: ... -def base_8_encode(bitstream: readable_bytearr) -> List[float]: ... -def base_8_decode(datastream: List[float]) -> readable_bytearr: ... -def base_16_encode(bitstream: readable_bytearr) -> List[float]: ... -def base_16_decode(datastream: List[float]) -> readable_bytearr: ... -def base_32_encode(bitstream: readable_bytearr) -> List[float]: ... -def base_32_decode(datastream: List[float]) -> readable_bytearr: ... -def base_64_encode(bitstream: readable_bytearr) -> List[float]: ... -def base_64_decode(datastream: List[float]) -> readable_bytearr: ... -def base_256_encode(bitstream: readable_bytearr) -> List[float]: ... -def base_256_decode(datastream: List[float]) -> readable_bytearr: ... -def make_pad_array(datastream: List[float], pad_len: int): ... -def unpad_array(datastream: List[float]): ... - -encode_function_mappings: Dict[int,Callable[[readable_bytearr], List[float]]] -decode_function_mappings: Dict[int,Callable[[List[float]], readable_bytearr]] From 2ad0989fed49b4d355392d7bae449cca07408db5 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 24 Dec 2019 10:46:47 -0800 Subject: [PATCH 083/228] Install the provisional numpy-stubs package --- Pipfile | 4 ++++ Pipfile.lock | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index bc49a7b..12f4959 100644 --- a/Pipfile +++ b/Pipfile @@ -19,3 +19,7 @@ itikz = "*" [dev-packages.mypy] version = "*" python_version = ">='3.5'" + +[dev-packages.numpy-stubs] +git = "https://github.com/numpy/numpy-stubs.git" +editable = true diff --git a/Pipfile.lock b/Pipfile.lock index 4edb300..b5fccdd 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b469ccdf5c9302c68270bfa14668b4b645725ed69b075762890626b088a77fd1" + "sha256": "45085f0ab1475b83ab850026aca0372a593724617f2963d9b33a155a0257b574" }, "pipfile-spec": 6, "requires": {}, @@ -531,6 +531,37 @@ "index": "pypi", "version": "==0.5.0" }, + "numpy": { + "hashes": [ + "sha256:03bbde29ac8fba860bb2c53a1525b3604a9b60417855ac3119d89868ec6041c3", + "sha256:1baefd1fb4695e7f2e305467dbd876d765e6edd30c522894df76f8301efaee36", + "sha256:1c35fb1131362e6090d30286cfda52ddd42e69d3e2bf1fea190a0fad83ea3a18", + "sha256:3c68c827689ca0ca713dba598335073ce0966850ec0b30715527dce4ecd84055", + "sha256:443ab93fc35b31f01db8704681eb2fd82f3a1b2fa08eed2dd0e71f1f57423d4a", + "sha256:56710a756c5009af9f35b91a22790701420406d9ac24cf6b652b0e22cfbbb7ff", + "sha256:62506e9e4d2a39c87984f081a2651d4282a1d706b1a82fe9d50a559bb58e705a", + "sha256:6f8113c8dbfc192b58996ee77333696469ea121d1c44ea429d8fd266e4c6be51", + "sha256:712f0c32555132f4b641b918bdb1fd3c692909ae916a233ce7f50eac2de87e37", + "sha256:854f6ed4fa91fa6da5d764558804ba5b0f43a51e5fe9fc4fdc93270b052f188a", + "sha256:88c5ccbc4cadf39f32193a5ef22e3f84674418a9fd877c63322917ae8f295a56", + "sha256:905cd6fa6ac14654a6a32b21fad34670e97881d832e24a3ca32e19b455edb4a8", + "sha256:9d6de2ad782aae68f7ed0e0e616477fbf693d6d7cc5f0f1505833ff12f84a673", + "sha256:a30f5c3e1b1b5d16ec1f03f4df28e08b8a7529d8c920bbed657f4fde61f1fbcd", + "sha256:a9d72d9abaf65628f0f31bbb573b7d9304e43b1e6bbae43149c17737a42764c4", + "sha256:ac3cf835c334fcc6b74dc4e630f9b5ff7b4c43f7fb2a7813208d95d4e10b5623", + "sha256:b091e5d4cbbe79f0e8b6b6b522346e54a282eadb06e3fd761e9b6fafc2ca91ad", + "sha256:cc070fc43a494e42732d6ae2f6621db040611c1dde64762a40c8418023af56d7", + "sha256:e1080e37c090534adb2dd7ae1c59ee883e5d8c3e63d2a4d43c20ee348d0459c5", + "sha256:f084d513de729ff10cd72a1f80db468cff464fedb1ef2fea030221a0f62d7ff4", + "sha256:f6a7421da632fc01e8a3ecd19c3f7350258d82501a646747664bae9c6a87c731" + ], + "index": "pypi", + "version": "==1.18.0" + }, + "numpy-stubs": { + "editable": true, + "git": "https://github.com/numpy/numpy-stubs.git" + }, "packaging": { "hashes": [ "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", From f66e9e0c38f8c01816a41b24121764c6a7ae6ae2 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 24 Dec 2019 10:47:14 -0800 Subject: [PATCH 084/228] Add stubs for used SciPy functions Waiting on better stubs for both SciPy and NumPy --- voicechat_modem_dsp/stubs/README.md | 1 + voicechat_modem_dsp/stubs/scipy/__init__.pyi | 0 voicechat_modem_dsp/stubs/scipy/interpolate.pyi | 13 +++++++++++++ .../stubs/scipy/signal/__init__.pyi | 15 +++++++++++++++ .../stubs/scipy/signal/windows.pyi | 7 +++++++ 5 files changed, 36 insertions(+) create mode 100644 voicechat_modem_dsp/stubs/README.md create mode 100644 voicechat_modem_dsp/stubs/scipy/__init__.pyi create mode 100644 voicechat_modem_dsp/stubs/scipy/interpolate.pyi create mode 100644 voicechat_modem_dsp/stubs/scipy/signal/__init__.pyi create mode 100644 voicechat_modem_dsp/stubs/scipy/signal/windows.pyi diff --git a/voicechat_modem_dsp/stubs/README.md b/voicechat_modem_dsp/stubs/README.md new file mode 100644 index 0000000..b348990 --- /dev/null +++ b/voicechat_modem_dsp/stubs/README.md @@ -0,0 +1 @@ +This directory contains stubs for library functions that do not have stubs yet. diff --git a/voicechat_modem_dsp/stubs/scipy/__init__.pyi b/voicechat_modem_dsp/stubs/scipy/__init__.pyi new file mode 100644 index 0000000..e69de29 diff --git a/voicechat_modem_dsp/stubs/scipy/interpolate.pyi b/voicechat_modem_dsp/stubs/scipy/interpolate.pyi new file mode 100644 index 0000000..b0ed254 --- /dev/null +++ b/voicechat_modem_dsp/stubs/scipy/interpolate.pyi @@ -0,0 +1,13 @@ +from typing import MutableSequence, Union, Tuple, Callable + +from numpy import ndarray + +array_like=Union[float,MutableSequence[float],MutableSequence[int],ndarray] + +# +def interp1d(x: array_like, y: array_like, + kind: Union[str,int]=..., axis: int=..., + copy: bool=..., bounds_error: bool=..., + fill_value: Union[array_like,Tuple[array_like,array_like],str]=..., + assume_sorted: bool=...) -> \ + Union[Callable[[float],float], Callable[[ndarray],ndarray]]: ... \ No newline at end of file diff --git a/voicechat_modem_dsp/stubs/scipy/signal/__init__.pyi b/voicechat_modem_dsp/stubs/scipy/signal/__init__.pyi new file mode 100644 index 0000000..ff34856 --- /dev/null +++ b/voicechat_modem_dsp/stubs/scipy/signal/__init__.pyi @@ -0,0 +1,15 @@ +from typing import Any, List, Union, Optional, Tuple, Callable + +from numpy import ndarray + +from . import windows + +# TODO: use ndarray instead? +floatpoint_t = Union[float, complex] + +def firls(numtaps: int, bands: List[float], weight: List[float] = ..., + nyq: float=..., fs: float=...) -> List[float]: ... +def freqz(b: List[floatpoint_t], a: List[floatpoint_t]=..., + worN: Union[int,List[float]]=..., whole: bool=..., + plot: Callable[[List[float],List[float]],Any]=..., + fs: float=...) -> Tuple[List[float],List[floatpoint_t]]: ... \ No newline at end of file diff --git a/voicechat_modem_dsp/stubs/scipy/signal/windows.pyi b/voicechat_modem_dsp/stubs/scipy/signal/windows.pyi new file mode 100644 index 0000000..ca88647 --- /dev/null +++ b/voicechat_modem_dsp/stubs/scipy/signal/windows.pyi @@ -0,0 +1,7 @@ +from typing import List + +from numpy import ndarray + +# TODO: use ndarray instead? + +def gaussian(M: int, sigma: float, sym: bool=...) -> ndarray: ... \ No newline at end of file From 7f490e4f53991940accd835f2c31d9185c96d5b1 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 24 Dec 2019 10:48:06 -0800 Subject: [PATCH 085/228] Use ndarray in stubs for modulator utils --- voicechat_modem_dsp/modulators/modulator_utils.pyi | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.pyi b/voicechat_modem_dsp/modulators/modulator_utils.pyi index 420263c..637ef41 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.pyi +++ b/voicechat_modem_dsp/modulators/modulator_utils.pyi @@ -1,14 +1,16 @@ # Stubs for modulators.modulator_utils (Python 3) -from typing import Any +from typing import Any, Sequence -def generate_timearray(fs: float, sample_count: int): ... +from numpy import ndarray + +def generate_timearray(fs: float, sample_count: int) -> ndarray: ... def samples_per_symbol(fs: float, baud: float) -> float: ... -def previous_resample_interpolate(timeseq: Any, baud: float, sequence: Any): ... -def average_interval_data(data: Any, begin: float, end: float) -> float: ... +def previous_resample_interpolate(timeseq: ndarray, baud: float, sequence: Sequence[float]) -> ndarray: ... +def average_interval_data(data: Sequence[float], begin: float, end: float) -> float: ... -def gaussian_window(fs: float, sigma_dt: float): ... +def gaussian_window(fs: float, sigma_dt: float) -> ndarray: ... def fred_harris_fir_tap_count(fs: float, transition_width: float, db_attenuation: float) -> int: ... -def lowpass_fir_filter(fs: float, cutoff_low: float, cutoff_high: float, attenuation: float = ...): ... +def lowpass_fir_filter(fs: float, cutoff_low: float, cutoff_high: float, attenuation: float = ...) -> ndarray: ... def linearize_fir(filter: Any) -> None: ... From 566b83404425fbb80fd71c2fd2ec6fc610e17781 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 24 Dec 2019 12:59:57 -0800 Subject: [PATCH 086/228] Move modulator_base type hints into actual file --- voicechat_modem_dsp/modulators/modulator_base.py | 8 +++++--- voicechat_modem_dsp/modulators/modulator_base.pyi | 11 ----------- 2 files changed, 5 insertions(+), 14 deletions(-) delete mode 100644 voicechat_modem_dsp/modulators/modulator_base.pyi diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index b390959..1133517 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -1,15 +1,17 @@ -import numpy as np +from numpy import ndarray from abc import ABC, abstractmethod +from typing import Sequence + """ Base class for other modulator objects Also contains useful helper functions as static methods """ class Modulator(ABC): @abstractmethod - def modulate(self, datastream): + def modulate(self, datastream: Sequence[int]) -> ndarray: raise NotImplementedError @abstractmethod - def demodulate(self, modulated_data): + def demodulate(self, modulated_data: ndarray) -> Sequence[int]: raise NotImplementedError diff --git a/voicechat_modem_dsp/modulators/modulator_base.pyi b/voicechat_modem_dsp/modulators/modulator_base.pyi deleted file mode 100644 index 24c8978..0000000 --- a/voicechat_modem_dsp/modulators/modulator_base.pyi +++ /dev/null @@ -1,11 +0,0 @@ -# Stubs for modulators.modulator_base (Python 3) - -import abc -from abc import ABC, abstractmethod -from typing import Any - -class Modulator(ABC, metaclass=abc.ABCMeta): - @abstractmethod - def modulate(self, datastream: Any) -> Any: ... - @abstractmethod - def demodulate(self, modulated_data: Any) -> Any: ... From c13b943ca374a7cf183e2881893bff3b063c8eb9 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 24 Dec 2019 13:09:59 -0800 Subject: [PATCH 087/228] Remove type declarations from encode_pad for Python 3.5 --- voicechat_modem_dsp/encoders/encode_pad.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/encoders/encode_pad.py b/voicechat_modem_dsp/encoders/encode_pad.py index 466964d..b3a79c8 100644 --- a/voicechat_modem_dsp/encoders/encode_pad.py +++ b/voicechat_modem_dsp/encoders/encode_pad.py @@ -180,8 +180,8 @@ def base_256_decode(datastream: List[int]) -> readable_bytearr: raise ValueError("Illegal symbol detected in datastream") # Convenience mapping to allow for lookup based on len(modulation_list) -encode_function_mappings: Dict[int,Callable[[readable_bytearr], List[int]]] -decode_function_mappings: Dict[int,Callable[[List[int]], readable_bytearr]] +#encode_function_mappings: Dict[int,Callable[[readable_bytearr], List[int]]] +#decode_function_mappings: Dict[int,Callable[[List[int]], readable_bytearr]] encode_function_mappings = {2:base_2_encode, 4:base_4_encode, 8:base_8_encode, 16:base_16_encode, 32:base_32_encode, 64:base_64_encode, From e5576d2c1cd33bc602713ef65c669cadfc69b6cc Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 24 Dec 2019 13:26:38 -0800 Subject: [PATCH 088/228] Install numpy-stubs and set up env matrix for Travis CI --- .travis.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1dd0729..1e48d26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,18 @@ addons: packages: - liblapack-dev +matrix: + include: + - python: '3.8' + os: linux + - python: '3.7' + env: MYPY=true + os: linux + - python: '3.6' + os: linux + - python: '3.5' + os: linux + python: - "3.8" - "3.7" @@ -25,11 +37,11 @@ install: - cat requirements.txt - pip install -r requirements.txt - pip install pytest coverage - - if [ $TRAVIS_PYTHON_VERSION = "3.7" ]; then pip install mypy; fi + - if [ $MYPY = true ]; then pip install mypy; pip install -e git+https://github.com/numpy/numpy-stubs.git; fi script: - sh ./run_tests_coverage.sh - - if [ $TRAVIS_PYTHON_VERSION = "3.7" ]; then sh ./run_mypy.sh; fi + - if [ $MYPY = true ]; then sh ./run_mypy.sh; fi after_script: - wget -O codecov_upload "https://codecov.io/bash" From 3d984347344aa50ed8ed664f43a42141f82d6dde Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 24 Dec 2019 13:28:19 -0800 Subject: [PATCH 089/228] Remove python keys left accidentally --- .travis.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1e48d26..53ad839 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ addons: packages: - liblapack-dev +#TODO: properly troubleshoot mypy permission errors matrix: include: - python: '3.8' @@ -21,13 +22,6 @@ matrix: - python: '3.5' os: linux -python: - - "3.8" - - "3.7" -# - "pypy3" - - "3.6" - - "3.5" - cache: pip install: From 6a6e2367c29070bdb857a425b9245dab80d0ea3e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 24 Dec 2019 13:32:18 -0800 Subject: [PATCH 090/228] Specify egg for numpy-stubs url What is Python's eggness percentage? Time for mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm facts --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 53ad839..cda9410 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ install: - cat requirements.txt - pip install -r requirements.txt - pip install pytest coverage - - if [ $MYPY = true ]; then pip install mypy; pip install -e git+https://github.com/numpy/numpy-stubs.git; fi + - if [ $MYPY = true ]; then pip install mypy; pip install -e git+https://github.com/numpy/numpy-stubs.git#egg=numpy-stubs; fi script: - sh ./run_tests_coverage.sh From 2d6e65a16a35616578f0496938d41911cc11de88 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 24 Dec 2019 13:38:10 -0800 Subject: [PATCH 091/228] Remove liblapack-dev since Scipy now supports 3.8 --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index cda9410..880a335 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,6 @@ language: python notifications: email: false -# Need this for now to build SciPy for Python 3.8 -addons: - apt: - packages: - - liblapack-dev - #TODO: properly troubleshoot mypy permission errors matrix: include: From b1204a53602b1832006356b980aa00006972fa69 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 24 Dec 2019 13:38:32 -0800 Subject: [PATCH 092/228] Double-quote the $MYPY if statement tests --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 880a335..81dc8d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,11 +25,11 @@ install: - cat requirements.txt - pip install -r requirements.txt - pip install pytest coverage - - if [ $MYPY = true ]; then pip install mypy; pip install -e git+https://github.com/numpy/numpy-stubs.git#egg=numpy-stubs; fi + - if [ "$MYPY" = true ]; then pip install mypy; pip install -e git+https://github.com/numpy/numpy-stubs.git#egg=numpy-stubs; fi script: - sh ./run_tests_coverage.sh - - if [ $MYPY = true ]; then sh ./run_mypy.sh; fi + - if [ "$MYPY" = true ]; then sh ./run_mypy.sh; fi after_script: - wget -O codecov_upload "https://codecov.io/bash" From 4b90d585c48410b295881969d31ae0fa3931348d Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 24 Dec 2019 17:55:00 -0800 Subject: [PATCH 093/228] Install cleo as CLI interface package --- Pipfile | 1 + Pipfile.lock | 31 ++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 12f4959..3f333a1 100644 --- a/Pipfile +++ b/Pipfile @@ -8,6 +8,7 @@ numpy = "*" scipy = "*" protobuf = "*" ipykernel = "*" +cleo = "*" [dev-packages] pytest = "*" diff --git a/Pipfile.lock b/Pipfile.lock index b5fccdd..7721d45 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "45085f0ab1475b83ab850026aca0372a593724617f2963d9b33a155a0257b574" + "sha256": "235668d386b57cf2b1e1388113ae804e484606cb58386ad1c6b108f97162ed8b" }, "pipfile-spec": 6, "requires": {}, @@ -21,6 +21,21 @@ ], "version": "==0.1.0" }, + "cleo": { + "hashes": [ + "sha256:9443d67e5b2da79b32d820ae41758dd6a25618345cb10b9a022a695e26b291b9", + "sha256:99cf342406f3499cec43270fcfaf93c126c5164092eca201dfef0f623360b409" + ], + "index": "pypi", + "version": "==0.7.6" + }, + "clikit": { + "hashes": [ + "sha256:80b0bfee42310a715773dded69590c4c33fa9fc9a351fa7c262cb67f21d0758f", + "sha256:8ae4766b974d7b1983e39d501da9a0aadf118a907a0c9b50714d027c8b59ea81" + ], + "version": "==0.4.1" + }, "decorator": { "hashes": [ "sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", @@ -105,6 +120,13 @@ ], "version": "==0.5.2" }, + "pastel": { + "hashes": [ + "sha256:a904e1659512cc9880a028f66de77cc813a4c32f7ceb68725cbc8afad57ef7ef", + "sha256:bf3b1901b2442ea0d8ab9a390594e5b0c9584709d543a3113506fe8b28cbace3" + ], + "version": "==0.1.1" + }, "pexpect": { "hashes": [ "sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", @@ -166,6 +188,13 @@ ], "version": "==2.5.2" }, + "pylev": { + "hashes": [ + "sha256:063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3", + "sha256:1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e" + ], + "version": "==1.3.0" + }, "python-dateutil": { "hashes": [ "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", From 0089da4b1697d7bd865cd0686ee2898f57df9b43 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 26 Dec 2019 21:16:33 -0800 Subject: [PATCH 094/228] Create preliminary config file spec --- docs/index.rst | 2 +- docs/specs/config.rst | 47 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 docs/specs/config.rst diff --git a/docs/index.rst b/docs/index.rst index 8391cd3..ebda697 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,8 +10,8 @@ Welcome to Voicechat Modem (DSP Component)'s documentation! :maxdepth: 2 :caption: Contents: - notebooks/Trapezoidal Averaging notebooks/Pre-Modulation Gaussian Smoothing + specs/config Indices and tables ================== diff --git a/docs/specs/config.rst b/docs/specs/config.rst new file mode 100644 index 0000000..ac592f7 --- /dev/null +++ b/docs/specs/config.rst @@ -0,0 +1,47 @@ +Specification of the Configuration File +======================================= + +The configuration file is written using the `StrictYaml `_ subset of YAML. + +The configuration file first specifies the sampling rate and the error correction code to use. After this, the config file specifies modulation mode specific parameters. + +Error Correction Codes +---------------------- + +Error Correction Code (ECC) choice is set with the ``ecc:`` key. +Supported Error Correction Codes: + +- None (``none`` or ``raw``) +- Hamming (7,4) (``hamming_7_4``) + +Modulation Modes +---------------- + +Modulation modes are set by including one of the keys named below. +Supported modulation modes: + +- Amplitude Shift Keying (``ask``) + +Modes that will be supported in the future: + +- Phase Shift Keying (``psk``) +- Quadrature Amplitude Modulation (``qam``) +- Frequency Shift Keying (``fsk``) + +Amplitude Shift Keying +~~~~~~~~~~~~~~~~~~~~~~ + +The parameters for the ``ask`` key are as follows: + +- ``carrier:`` The carrier frequency +- ``amplitudes:`` A list of amplitudes between 0 and 1 +- ``baud:`` The symbol rate + +Phase Shift Keying +~~~~~~~~~~~~~~~~~~ + +The parameters for the ``psk`` key are as follows: + +- ``carrier:`` The carrier frequency +- ``phases:`` A list of phases between 0 and 1 (internally multiplied by 2Ï€) +- ``baud:`` The symbol rate \ No newline at end of file From e7a2f1b4814e36cdf7ac35d9484c7c468ca71048 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 26 Dec 2019 21:16:58 -0800 Subject: [PATCH 095/228] Start creating CLI command for transmitting a file --- voicechat_modem_dsp/__main__.py | 10 ++++++ voicechat_modem_dsp/cli/__init__.py | 0 voicechat_modem_dsp/cli/command_objects.py | 38 ++++++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 voicechat_modem_dsp/__main__.py create mode 100644 voicechat_modem_dsp/cli/__init__.py create mode 100644 voicechat_modem_dsp/cli/command_objects.py diff --git a/voicechat_modem_dsp/__main__.py b/voicechat_modem_dsp/__main__.py new file mode 100644 index 0000000..420495f --- /dev/null +++ b/voicechat_modem_dsp/__main__.py @@ -0,0 +1,10 @@ +from cleo import Application + +from .cli.command_objects import TxFile + +app=Application() + +app.add(TxFile()) + +if __name__=="__main__": + app.run() \ No newline at end of file diff --git a/voicechat_modem_dsp/cli/__init__.py b/voicechat_modem_dsp/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py new file mode 100644 index 0000000..23aef7e --- /dev/null +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -0,0 +1,38 @@ +import cleo + +import os + +class TxFile(cleo.Command): + """ + Modulates a given datafile and saves modulated audio to an audio file + + transmit_file + {filename : Data file to modulate} + {--o|output=modulated.wav : Output audio file } + {--config= : Configuration file} + """ + + def handle(self): + config_file_name=self.option("config") + output_file_name=self.option("output") + self.line("Test base command") + if not config_file_name or not os.path.isfile(config_file_name): + self.line_error("A valid configuration file must be specified.", + "error") + return 1 + + if os.path.exists(output_file_name): + if os.path.isdir(output_file_name): + self.line_error("Output file {} must be writable as a file." + .format(output_file_name),"error") + return 1 + + if self._io.is_interactive(): + result=self.confirm("Output file {} already exists. Overwrite?" + .format(output_file_name)) + self.line(str(result)) + else: + self.line_error("Output file {} already exists " + "and program is in noninteractive mode." + .format(output_file_name),"error") + return 1 From 9f9069191883a5f38b4a57fd3c30e922dd2ca308 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 27 Dec 2019 18:41:38 -0800 Subject: [PATCH 096/228] Install the StrictYaml package --- Pipfile | 1 + Pipfile.lock | 53 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/Pipfile b/Pipfile index 3f333a1..aa2e94c 100644 --- a/Pipfile +++ b/Pipfile @@ -9,6 +9,7 @@ scipy = "*" protobuf = "*" ipykernel = "*" cleo = "*" +strictyaml = "*" [dev-packages] pytest = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 7721d45..cabff5d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "235668d386b57cf2b1e1388113ae804e484606cb58386ad1c6b108f97162ed8b" + "sha256": "efd4a81e6dc69ab8299984822a50abf0c6b9527500539faaf952d45d1bb977c5" }, "pipfile-spec": 6, "requires": {}, @@ -235,6 +235,38 @@ ], "version": "==18.1.1" }, + "ruamel.yaml": { + "hashes": [ + "sha256:0db639b1b2742dae666c6fc009b8d1931ef15c9276ef31c0673cc6dcf766cf40", + "sha256:412a6f5cfdc0525dee6a27c08f5415c7fd832a7afcb7a0ed7319628aed23d408" + ], + "version": "==0.16.5" + }, + "ruamel.yaml.clib": { + "hashes": [ + "sha256:1e77424825caba5553bbade750cec2277ef130647d685c2b38f68bc03453bac6", + "sha256:392b7c371312abf27fb549ec2d5e0092f7ef6e6c9f767bfb13e83cb903aca0fd", + "sha256:4d55386129291b96483edcb93b381470f7cd69f97585829b048a3d758d31210a", + "sha256:550168c02d8de52ee58c3d8a8193d5a8a9491a5e7b2462d27ac5bf63717574c9", + "sha256:57933a6986a3036257ad7bf283529e7c19c2810ff24c86f4a0cfeb49d2099919", + "sha256:615b0396a7fad02d1f9a0dcf9f01202bf9caefee6265198f252c865f4227fcc6", + "sha256:77556a7aa190be9a2bd83b7ee075d3df5f3c5016d395613671487e79b082d784", + "sha256:7aee724e1ff424757b5bd8f6c5bbdb033a570b2b4683b17ace4dbe61a99a657b", + "sha256:8073c8b92b06b572e4057b583c3d01674ceaf32167801fe545a087d7a1e8bf52", + "sha256:9c6d040d0396c28d3eaaa6cb20152cb3b2f15adf35a0304f4f40a3cf9f1d2448", + "sha256:a0ff786d2a7dbe55f9544b3f6ebbcc495d7e730df92a08434604f6f470b899c5", + "sha256:b1b7fcee6aedcdc7e62c3a73f238b3d080c7ba6650cd808bce8d7761ec484070", + "sha256:b66832ea8077d9b3f6e311c4a53d06273db5dc2db6e8a908550f3c14d67e718c", + "sha256:be018933c2f4ee7de55e7bd7d0d801b3dfb09d21dad0cce8a97995fd3e44be30", + "sha256:d0d3ac228c9bbab08134b4004d748cf9f8743504875b3603b3afbb97e3472947", + "sha256:d10e9dd744cf85c219bf747c75194b624cc7a94f0c80ead624b06bfa9f61d3bc", + "sha256:ea4362548ee0cbc266949d8a441238d9ad3600ca9910c3fe4e82ee3a50706973", + "sha256:ed5b3698a2bb241b7f5cbbe277eaa7fe48b07a58784fba4f75224fd066d253ad", + "sha256:f9dcc1ae73f36e8059589b601e8e4776b9976effd76c21ad6a855a74318efd6e" + ], + "markers": "platform_python_implementation == 'CPython' and python_version < '3.8'", + "version": "==0.2.0" + }, "scipy": { "hashes": [ "sha256:00af72998a46c25bdb5824d2b729e7dabec0c765f9deb0b504f928591f5ff9d4", @@ -269,6 +301,13 @@ ], "version": "==1.13.0" }, + "strictyaml": { + "hashes": [ + "sha256:dd687a32577e0b832619ce0552eac86d6afad5fa7b61ab041bb765881c6a1f36" + ], + "index": "pypi", + "version": "==1.0.6" + }, "tornado": { "hashes": [ "sha256:349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c", @@ -414,10 +453,10 @@ }, "imagesize": { "hashes": [ - "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", - "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" + "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", + "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1" ], - "version": "==1.1.0" + "version": "==1.2.0" }, "importlib-metadata": { "hashes": [ @@ -627,10 +666,10 @@ }, "pyparsing": { "hashes": [ - "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", - "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" + "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", + "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" ], - "version": "==2.4.5" + "version": "==2.4.6" }, "pyrsistent": { "hashes": [ From 8fb7740fb5afc37ebef5db15461380b6cec91637 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 28 Dec 2019 18:26:56 -0800 Subject: [PATCH 097/228] Change config spec slightly as details worked out --- docs/specs/config.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/specs/config.rst b/docs/specs/config.rst index ac592f7..71dcf68 100644 --- a/docs/specs/config.rst +++ b/docs/specs/config.rst @@ -1,9 +1,11 @@ -Specification of the Configuration File -======================================= +Specification of the Configuration File (Version 0.1) +===================================================== The configuration file is written using the `StrictYaml `_ subset of YAML. -The configuration file first specifies the sampling rate and the error correction code to use. After this, the config file specifies modulation mode specific parameters. +The configuration file first specifies the config file version (``version``), +the sampling rate (``fs``), and the error correction code to use. +After this, the config file specifies modulation mode specific parameters. Error Correction Codes ---------------------- From f61f7d716318fec6b3c35d4d8ce0a11c30378e4a Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 28 Dec 2019 18:27:12 -0800 Subject: [PATCH 098/228] Add cli options to disable preamble and toneburst --- voicechat_modem_dsp/cli/command_objects.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py index 23aef7e..66bae04 100644 --- a/voicechat_modem_dsp/cli/command_objects.py +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -8,8 +8,11 @@ class TxFile(cleo.Command): transmit_file {filename : Data file to modulate} - {--o|output=modulated.wav : Output audio file } - {--config= : Configuration file} + {--o|output=modulated.wav : Output file for audio} + {--config= : Modulation configuration file} + {--no-preamble : Do not include audio preamble with + modulation information} + {--no-toneburst : Do not include calibration toneburst} """ def handle(self): From 9455e2fc7a9cfb183ee90cd3512d14212df2b8c7 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 28 Dec 2019 18:34:06 -0800 Subject: [PATCH 099/228] Move sigma constants into modulator base --- voicechat_modem_dsp/modulators/modulator_ask.py | 10 +++------- voicechat_modem_dsp/modulators/modulator_ask.pyi | 2 -- voicechat_modem_dsp/modulators/modulator_base.py | 5 +++++ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 3668635..eb0b852 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -10,10 +10,6 @@ import warnings class ASKModulator(Modulator): - # norm.isf(1/(2*2^8)) - sigma_mult_t=2.89 - # norm.isf(0.0001) - sigma_mult_f=3.72 def __init__(self, fs, carrier, amp_list, baud): if baud>=0.5*carrier: @@ -47,13 +43,13 @@ def __init__(self, fs, carrier, amp_list, baud): @property def _calculate_sigma(self): #sigma_t = w/4k, at most half of the pulse is smoothed away - gaussian_sigma_t=(1/self.baud)/(4*ASKModulator.sigma_mult_t) + gaussian_sigma_t=(1/self.baud)/(4*Modulator.sigma_mult_t) # Ensure dropoff at halfway to doubled carrier frequency is -80dB # This is not the same as carrier_freq because Nyquist reflections doubled_carrier_refl=min( 2*self.carrier_freq,self.fs-2*self.carrier_freq) halfway_thresh=0.5*doubled_carrier_refl - gaussian_sigma_f=ASKModulator.sigma_mult_f/(2*np.pi*halfway_thresh) + gaussian_sigma_f=Modulator.sigma_mult_f/(2*np.pi*halfway_thresh) return min(gaussian_sigma_t,gaussian_sigma_f) # Expose modulator_utils calculation here as OOP read-only property @@ -117,7 +113,7 @@ def demodulate(self, modulated_data): list_amplitudes=list() for i in range(interval_count): - transition_width=ASKModulator.sigma_mult_t*self._calculate_sigma + transition_width=Modulator.sigma_mult_t*self._calculate_sigma # Convert above time width into sample point width transition_width*=self.fs diff --git a/voicechat_modem_dsp/modulators/modulator_ask.pyi b/voicechat_modem_dsp/modulators/modulator_ask.pyi index dbecf44..bccbc80 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.pyi +++ b/voicechat_modem_dsp/modulators/modulator_ask.pyi @@ -4,8 +4,6 @@ from .modulator_base import Modulator from typing import Any, List, Dict, Tuple class ASKModulator(Modulator): - sigma_mult_t: float = ... - sigma_mult_f: float = ... fs: float = ... amp_list: Dict[int, float] = ... carrier_freq: float = ... diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index 1133517..1971111 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -9,6 +9,11 @@ Also contains useful helper functions as static methods """ class Modulator(ABC): + # norm.isf(1/(2*2^8)) + sigma_mult_t=2.89 + # norm.isf(0.0001) + sigma_mult_f=3.72 + @abstractmethod def modulate(self, datastream: Sequence[int]) -> ndarray: raise NotImplementedError From a36d215f949e2bbdd14346a1ba8e8f29fe1dded3 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 28 Dec 2019 18:38:40 -0800 Subject: [PATCH 100/228] Update type stubs for ASK Modulator Forgot to replace Anys with the appropriate sequence types --- voicechat_modem_dsp/modulators/modulator_ask.pyi | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.pyi b/voicechat_modem_dsp/modulators/modulator_ask.pyi index bccbc80..c1801e9 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.pyi +++ b/voicechat_modem_dsp/modulators/modulator_ask.pyi @@ -1,7 +1,9 @@ # Stubs for modulators.modulator_ask (Python 3) from .modulator_base import Modulator -from typing import Any, List, Dict, Tuple +from typing import Sequence, Dict, Tuple + +from numpy import ndarray class ASKModulator(Modulator): fs: float = ... @@ -9,6 +11,6 @@ class ASKModulator(Modulator): carrier_freq: float = ... baud: float = ... def __init__(self, fs: float, carrier: float, - amp_list: List[float], baud: float) -> None: ... - def modulate(self, datastream: Any) -> Any: ... - def demodulate(self, modulated_data: Any) -> Any: ... + amp_list: Sequence[float], baud: float) -> None: ... + def modulate(self, datastream: Sequence[int]) -> ndarray: ... + def demodulate(self, modulated_data: ndarray) -> Sequence[int]: ... From b1980e5a15ade96a26e2cc9cd4c64a7d040b3e03 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 29 Dec 2019 10:52:54 -0800 Subject: [PATCH 101/228] Remove _samples_per_symbol @property and add ModulationIntegrityWarning checks --- .../modulators/modulator_ask.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index eb0b852..d9fd55b 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -52,13 +52,8 @@ def _calculate_sigma(self): gaussian_sigma_f=Modulator.sigma_mult_f/(2*np.pi*halfway_thresh) return min(gaussian_sigma_t,gaussian_sigma_f) - # Expose modulator_utils calculation here as OOP read-only property - @property - def _samples_per_symbol(self): - return modulator_utils.samples_per_symbol(self.fs,self.baud) - def modulate(self, datastream): - # Compute gaussian smoothing kernel + samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) gaussian_sigma=self._calculate_sigma gaussian_window=modulator_utils.gaussian_window(self.fs,gaussian_sigma) @@ -68,7 +63,7 @@ def modulate(self, datastream): # Upsample amplitude to actual sampling rate interp_sample_count=int(np.ceil( - len(amplitude_data)*self._samples_per_symbol)) + len(amplitude_data)*samples_per_symbol)) time_array=modulator_utils.generate_timearray( self.fs,interp_sample_count) interpolated_amplitude=modulator_utils.previous_resample_interpolate( @@ -82,7 +77,7 @@ def modulate(self, datastream): np.cos(2*np.pi*self.carrier_freq*time_array) def demodulate(self, modulated_data): - # TODO: find an easier robust way to demodulate? + samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) # Use complex exponential to allow for phase drift time_array=modulator_utils.generate_timearray( self.fs,len(modulated_data)) @@ -108,7 +103,7 @@ def demodulate(self, modulated_data): # Round to account for floating point weirdness interval_count=int(np.round( - len(modulated_data)/self._samples_per_symbol)) + len(modulated_data)/samples_per_symbol)) interval_offset=filt_delay list_amplitudes=list() @@ -117,9 +112,9 @@ def demodulate(self, modulated_data): # Convert above time width into sample point width transition_width*=self.fs - interval_begin=interval_offset+i*self._samples_per_symbol + interval_begin=interval_offset+i*samples_per_symbol # Perform min in order to account for floating point weirdness - interval_end=min(interval_begin+self._samples_per_symbol, + interval_end=min(interval_begin+samples_per_symbol, len(modulated_data)-1) # Shrink interval by previously calculated transition width @@ -144,4 +139,8 @@ def demodulate(self, modulated_data): # Subtract data points by 1 and remove 0 padding # Neat side effect: -1 is an invalid data point datastream=vector_cluster[0]-1 + if (datastream[0]!=-1 or datastream[-1]!=-1 + or any(datastream[1:-1]==-1)): + warnings.warn("Corrupted datastream detected while demodulating", + ModulationIntegrityWarning) return datastream[1:-1] From 66f6578c84b1abdc74639b846b9148500537cfc7 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 29 Dec 2019 10:54:50 -0800 Subject: [PATCH 102/228] Slightly rearrange average_interval_data calculations --- voicechat_modem_dsp/modulators/modulator_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 5c00bcf..e9cec7d 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -75,9 +75,9 @@ def average_interval_data(data, begin, end): # Construct input to numpy.trapz x_array=list(range(int(np.ceil(begin)),int(np.floor(end))+1)) - x_array=np.asarray([begin]+x_array+[end]) + y_array=[data[i] for i in x_array] - y_array=[data[i] for i in range(int(np.ceil(begin)),int(np.floor(end))+1)] + x_array=np.asarray([begin]+x_array+[end]) y_array=np.asarray([begin_lininterp]+y_array+[end_lininterp]) return np.trapz(y_array,x_array)/width From 657fd590c495301d0b89375d392e9fbfddfe7c09 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 29 Dec 2019 11:58:21 -0800 Subject: [PATCH 103/228] Miscellaneous comment cleanup --- voicechat_modem_dsp/modulators/modulator_ask.pyi | 2 -- voicechat_modem_dsp/modulators/modulator_base.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.pyi b/voicechat_modem_dsp/modulators/modulator_ask.pyi index c1801e9..b65e0de 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.pyi +++ b/voicechat_modem_dsp/modulators/modulator_ask.pyi @@ -1,5 +1,3 @@ -# Stubs for modulators.modulator_ask (Python 3) - from .modulator_base import Modulator from typing import Sequence, Dict, Tuple diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index 1971111..231e4af 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -6,7 +6,7 @@ """ Base class for other modulator objects -Also contains useful helper functions as static methods +Also contains useful helpers as static attributes """ class Modulator(ABC): # norm.isf(1/(2*2^8)) From 9d8ae7176e1ae0ea574d9940a30dec156fb0a9d6 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 29 Dec 2019 11:58:40 -0800 Subject: [PATCH 104/228] New stub for FSKModulator --- voicechat_modem_dsp/modulators/modulator_fsk.pyi | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 voicechat_modem_dsp/modulators/modulator_fsk.pyi diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.pyi b/voicechat_modem_dsp/modulators/modulator_fsk.pyi new file mode 100644 index 0000000..01c15a7 --- /dev/null +++ b/voicechat_modem_dsp/modulators/modulator_fsk.pyi @@ -0,0 +1,14 @@ +from .modulator_base import Modulator +from typing import Sequence, Dict, Tuple + +from numpy import ndarray + +class FSKModulator(Modulator): + fs: float = ... + freq_list: Dict[int, float] = ... + amplitude: float = ... + baud: float = ... + def __init__(self, fs: float, amplitude: float, + freq_list: Sequence[float], baud: float) -> None: ... + def modulate(self, datastream: Sequence[int]) -> ndarray: ... + def demodulate(self, modulated_data: ndarray) -> Sequence[int]: ... From b4aa766f40ccb7b4eed65ec91f6b16a7cbf9a82c Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 29 Dec 2019 11:59:21 -0800 Subject: [PATCH 105/228] Write modulation portion of FSKModulator --- .../modulators/modulator_fsk.py | 85 ++++++++++++++++--- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index 84ab2ee..cbabc82 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -1,21 +1,80 @@ from .modulator_base import Modulator +from . import modulator_utils +from .modulator_utils import ModulationIntegrityWarning + +import numpy as np +from scipy import signal +import scipy.integrate + +import warnings + class FSKModulator(Modulator): - def __init__(self, fs, freq_list, baud): - if baud>=0.5*max(freq_list): + def __init__(self, fs, amplitude, freq_list, baud): + if baud>min(freq_list): raise ValueError("Baud is too high to be modulated "+ "using given frequencies") - if fs<=2*max(freq_list): - raise ValueError + if min(freq_list)<=0: + raise ValueError("Invalid frequencies given") + # Nyquist limit + if max(freq_list)>=0.5*fs: + raise ValueError("Maximum frequency is too high for sampling rate") + if amplitude>1 or amplitude<=0: + raise ValueError("Base amplitude must be positive and at most 1") + self.fs=fs - self.freq_list=freq_list + self.amplitude=amplitude + self.freq_list=dict(enumerate(freq_list)) self.baud=baud - - def modulate(self, data): - timesamples_needed=len(data)*self.samples_per_symbol - time_array=Modulator.generate_time_array(self.fs,timesamples_needed) - current_sin_timestamp=0 - # Incremental addition is a form of integration - # Frequency in the end is the derivative of phase - def demodulate(self, time_array, datastream): + + if amplitude<0.1: + warnings.warn("Amplitude may be too small to allow " + "reliable reconstruction",ModulationIntegrityWarning) + # TODO: additional warnings relating to Goertzel resolution, etc. + + @property + def _calculate_sigma(self): + #sigma_t = w/4k, at most half of the pulse is smoothed away + gaussian_sigma_t=(1/self.baud)/(4*Modulator.sigma_mult_t) + # TODO: test possibilities that make sense for FSK + gaussian_sigma_f=(2*np.pi/max(self.freq_list.values()))/Modulator.sigma_mult_t + return min(gaussian_sigma_t,gaussian_sigma_f) + + def modulate(self, datastream): + samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) + gaussian_sigma=self._calculate_sigma + gaussian_window=modulator_utils.gaussian_window(self.fs,gaussian_sigma) + + # Map datastream to frequencies and pad on both ends + # Exact padding does not matter because of amplitude shaping + frequency_data = np.pad([self.freq_list[datum] for datum in datastream], + 1,mode="constant",constant_values=0) + + # Upsample frequency to actual sampling rate + interp_sample_count=int(np.ceil( + len(frequency_data)*samples_per_symbol)) + time_array=modulator_utils.generate_timearray( + self.fs,interp_sample_count) + interpolated_frequency=modulator_utils.previous_resample_interpolate( + time_array, self.baud, frequency_data) + # Smooth frequencies with Gaussian kernel + shaped_frequency=signal.convolve(interpolated_frequency,gaussian_window, + "same",method="fft") + + # Construct smoothed amplitude mask + # TODO: be smarter about only convolving the edges + amplitude_mask=np.asarray([0]+[self.amplitude]*len(datastream)+[0]) + interpolated_amplitude=modulator_utils.previous_resample_interpolate( + time_array,self.baud,amplitude_mask) + shaped_amplitude=signal.convolve(interpolated_amplitude,gaussian_window, + "same",method="fft") + + # Frequency is the derivative of phase + phase_array=scipy.integrate.cumtrapz( + shaped_frequency,time_array,initial=0)%1 + phase_array*=2*np.pi + + return shaped_amplitude*np.cos(phase_array) + + def demodulate(self, modulated_data): raise NotImplementedError \ No newline at end of file From 2f422ac98b7fcc02c61183732ad7ee835f4fb194 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 31 Dec 2019 22:02:50 -0800 Subject: [PATCH 106/228] Create (at least superficially) working demodulator for FSK TODO: write integrity tests --- .../modulators/modulator_fsk.py | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index cbabc82..64d4d0a 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -6,6 +6,7 @@ import numpy as np from scipy import signal import scipy.integrate +from scipy.cluster.vq import vq import warnings @@ -39,6 +40,16 @@ def _calculate_sigma(self): # TODO: test possibilities that make sense for FSK gaussian_sigma_f=(2*np.pi/max(self.freq_list.values()))/Modulator.sigma_mult_t return min(gaussian_sigma_t,gaussian_sigma_f) + + @staticmethod + def _goertzel_iir(freq,fs): + # See https://www.dsprelated.com/showarticle/796.php for derivation + if freq>=0.5*fs: + raise ValueError("Desired peak frequency is too high") + norm_freq=2*np.pi*freq/fs + numerator=[1,-np.exp(-1j*norm_freq)] + denominator=[1,-2*np.cos(norm_freq),1] + return (numerator,denominator) def modulate(self, datastream): samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) @@ -77,4 +88,58 @@ def modulate(self, datastream): return shaped_amplitude*np.cos(phase_array) def demodulate(self, modulated_data): - raise NotImplementedError \ No newline at end of file + samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) + goertzel_filters={index: FSKModulator._goertzel_iir(freq,self.fs) + for index, freq in self.freq_list.items()} + list_frequencies=[self.freq_list[i] for i in range(len(self.freq_list))] + + interval_count=int(np.round( + len(modulated_data)/samples_per_symbol)) + goertzel_results=list() + for i in range(interval_count): + transition_width=Modulator.sigma_mult_t*self._calculate_sigma + # Convert above time width into sample point width + transition_width*=self.fs + + interval_begin=i*samples_per_symbol + # Perform min in order to account for floating point weirdness + interval_end=min(interval_begin+samples_per_symbol, + len(modulated_data)-1) + + # Shrink interval by previously calculated transition width + # Skip doing so for first and last sample + if i!=0: + interval_begin+=transition_width + if i!=interval_count-1: + interval_end-=transition_width + # Use np.floor and np.ceil to get integer indexes + # TODO: find elegant way to handle noninteger interval bounds + interval_begin=int(np.round(interval_begin)) + interval_end=int(np.round(interval_end)) + # Find the frequency using Goertzel "filter" + goertzel_result=list() + for index in range(len(self.freq_list)): + val=signal.lfilter(*goertzel_filters[index], + modulated_data[interval_begin:interval_end+1])[-1] + val=2*np.abs(val)/(interval_end-interval_begin) + goertzel_result.append(val) + + goertzel_results.append(goertzel_result) + + codebook_vectors=self.amplitude*np.identity(len(self.freq_list)) + codebook_vectors=np.insert(codebook_vectors, + 0,[0]*len(self.freq_list),axis=0) + #print(codebook_vectors) + #import pprint + #pprint.pprint(goertzel_results) + vector_cluster=vq(goertzel_results, codebook_vectors) + #pprint.pprint(vector_cluster) + + # Subtract data points by 1 and remove 0 padding + # Neat side effect: -1 is an invalid data point + datastream=vector_cluster[0]-1 + if (datastream[0]!=-1 or datastream[-1]!=-1 + or any(datastream[1:-1]==-1)): + warnings.warn("Corrupted datastream detected while demodulating", + ModulationIntegrityWarning) + return datastream[1:-1] From 56e9dcda6f6e9359adc57f0619decba0de2ad7b6 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 1 Jan 2020 15:32:04 -0800 Subject: [PATCH 107/228] Add frequency spacing warning and fix diff calculation --- voicechat_modem_dsp/modulators/modulator_ask.py | 4 ++-- voicechat_modem_dsp/modulators/modulator_fsk.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index d9fd55b..3e82cfa 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -34,8 +34,8 @@ def __init__(self, fs, carrier, amp_list, baud): warnings.warn("Some amplitudes may be too low " "to be distinguishable from background noise", ModulationIntegrityWarning) - if any(dx<=0.05 for dx in np.diff(amp_list)): - warnings.warn("Some amplitudes may be too close " + if any(dx<=0.05 for dx in np.diff(sorted(amp_list))): + warnings.warn("Amplitudes may be too close " "to be distinguishable from each other", ModulationIntegrityWarning) # TODO: additional warnings relating to filter overshoot and the like diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index 64d4d0a..1e2f0cf 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -31,6 +31,11 @@ def __init__(self, fs, amplitude, freq_list, baud): if amplitude<0.1: warnings.warn("Amplitude may be too small to allow " "reliable reconstruction",ModulationIntegrityWarning) + dft_min_freq_gap=0.5*fs/(fs/baud) + if any(dx<=dft_min_freq_gap for dx in np.diff(sorted(freq_list))): + warnings.warn("Frequencies may be too close " + "to be distinguishable from one another", + ModulationIntegrityWarning) # TODO: additional warnings relating to Goertzel resolution, etc. @property From f8184839ce349f54a6c72cd02ec21d9fa56e2969 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 1 Jan 2020 15:32:42 -0800 Subject: [PATCH 108/228] Write unit test for FSK using Bell 202 scheme --- test/test_modulator_integrity.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/test_modulator_integrity.py b/test/test_modulator_integrity.py index fa8fe53..d5e78fc 100644 --- a/test/test_modulator_integrity.py +++ b/test/test_modulator_integrity.py @@ -4,6 +4,7 @@ from voicechat_modem_dsp.encoders.encode_pad import * from voicechat_modem_dsp.modulators.modulator_ask import ASKModulator +from voicechat_modem_dsp.modulators.modulator_fsk import FSKModulator from voicechat_modem_dsp.modulators.modulator_utils import ModulationIntegrityWarning import pytest @@ -48,6 +49,25 @@ def test_unit_ask_integrity_voice(): assert bitstream==recovered_bitstream +@pytest.mark.unit +def test_unit_fsk_integrity_bell_202(): + frequency_list=[1200,2200] + # Shuffle as opposed to complete random to test all 0x00-0xff + list_data=list(range(256)) + bitstream=bytes(list_data) + datastream=base_2_encode(bitstream) + + sampling_freq=48000 + amplitude=0.5 + baud_rate=1200 + + modulator=FSKModulator(sampling_freq,amplitude,frequency_list,baud_rate) + modulated_data=modulator.modulate(datastream) + demodulated_datastream=modulator.demodulate(modulated_data) + recovered_bitstream=base_2_decode(demodulated_datastream) + + assert bitstream==recovered_bitstream + @pytest.mark.property def test_property_ask_integrity(): amplitude_list=list(np.linspace(0.1,1,16)) From 590c4148c3e6a457001cef30f830878ac79aec44 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 1 Jan 2020 15:51:03 -0800 Subject: [PATCH 109/228] Write some invalid parameter tests for FSK --- test/test_modulator_parameters.py | 11 +++++++++++ voicechat_modem_dsp/modulators/modulator_fsk.py | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/test/test_modulator_parameters.py b/test/test_modulator_parameters.py index 0ae13d8..e2c45e4 100644 --- a/test/test_modulator_parameters.py +++ b/test/test_modulator_parameters.py @@ -3,6 +3,7 @@ from voicechat_modem_dsp.encoders.encode_pad import * from voicechat_modem_dsp.modulators.modulator_ask import ASKModulator +from voicechat_modem_dsp.modulators.modulator_fsk import FSKModulator from voicechat_modem_dsp.modulators.modulator_utils import ModulationIntegrityWarning import pytest @@ -11,8 +12,11 @@ def test_invalid_nyquist(): with pytest.raises(ValueError, match=r"Carrier frequency is too high.+"): bad_modulator=ASKModulator(1000,600,np.linspace(0.2,1,8),20) + with pytest.raises(ValueError, match=r"Baud is too high.+"): bad_modulator=ASKModulator(1000,100,np.linspace(0.2,1,8),80) + with pytest.raises(ValueError, match=r"Baud is too high.+"): + bad_modulator=FSKModulator(1000,1,np.linspace(200,1000,8),800) with pytest.warns(ModulationIntegrityWarning): bad_modulator=ASKModulator(900,400,np.linspace(0.2,1,8),100) @@ -25,3 +29,10 @@ def test_invalid_modspecific(): bad_modulator=ASKModulator(2000,880,np.geomspace(0.01,0.5,4),40) with pytest.warns(ModulationIntegrityWarning): bad_modulator=ASKModulator(2000,880,np.geomspace(0.2,1,256),40) + + with pytest.raises(ValueError, match=r"Invalid frequencies.+"): + bad_modulator=FSKModulator(1000,1,np.linspace(-1,1000,16),500) + with pytest.raises(ValueError, match=r"amplitude must be positive.+"): + bad_modulator=FSKModulator(1000,-1,np.linspace(100,200,16),20) + with pytest.warns(ModulationIntegrityWarning): + bad_modulator=FSKModulator(1000,0.02,np.linspace(100,200,16),20) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index 1e2f0cf..a2ecde5 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -12,11 +12,11 @@ class FSKModulator(Modulator): def __init__(self, fs, amplitude, freq_list, baud): + if min(freq_list)<=0: + raise ValueError("Invalid frequencies given") if baud>min(freq_list): raise ValueError("Baud is too high to be modulated "+ "using given frequencies") - if min(freq_list)<=0: - raise ValueError("Invalid frequencies given") # Nyquist limit if max(freq_list)>=0.5*fs: raise ValueError("Maximum frequency is too high for sampling rate") From 72db0cb1ff77ecd3ac2a5d16562eb87ea22a1d5e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 1 Jan 2020 17:24:26 -0800 Subject: [PATCH 110/228] Clean up FSK and move Goertzel to utils --- voicechat_modem_dsp/modulators/modulator_fsk.py | 16 +--------------- .../modulators/modulator_utils.py | 9 +++++++++ .../modulators/modulator_utils.pyi | 5 ++++- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index a2ecde5..59799c9 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -45,16 +45,6 @@ def _calculate_sigma(self): # TODO: test possibilities that make sense for FSK gaussian_sigma_f=(2*np.pi/max(self.freq_list.values()))/Modulator.sigma_mult_t return min(gaussian_sigma_t,gaussian_sigma_f) - - @staticmethod - def _goertzel_iir(freq,fs): - # See https://www.dsprelated.com/showarticle/796.php for derivation - if freq>=0.5*fs: - raise ValueError("Desired peak frequency is too high") - norm_freq=2*np.pi*freq/fs - numerator=[1,-np.exp(-1j*norm_freq)] - denominator=[1,-2*np.cos(norm_freq),1] - return (numerator,denominator) def modulate(self, datastream): samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) @@ -94,7 +84,7 @@ def modulate(self, datastream): def demodulate(self, modulated_data): samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) - goertzel_filters={index: FSKModulator._goertzel_iir(freq,self.fs) + goertzel_filters={index: modulator_utils.goertzel_iir(freq,self.fs) for index, freq in self.freq_list.items()} list_frequencies=[self.freq_list[i] for i in range(len(self.freq_list))] @@ -134,11 +124,7 @@ def demodulate(self, modulated_data): codebook_vectors=self.amplitude*np.identity(len(self.freq_list)) codebook_vectors=np.insert(codebook_vectors, 0,[0]*len(self.freq_list),axis=0) - #print(codebook_vectors) - #import pprint - #pprint.pprint(goertzel_results) vector_cluster=vq(goertzel_results, codebook_vectors) - #pprint.pprint(vector_cluster) # Subtract data points by 1 and remove 0 padding # Neat side effect: -1 is an invalid data point diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index e9cec7d..6466537 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -140,3 +140,12 @@ def linearize_fir(fir_filter): frequencies,response=signal.freqz(fir_filter) # TODO fill in the rest of this code if this ends up being actually used raise NotImplementedError + +def goertzel_iir(freq,fs): + # See https://www.dsprelated.com/showarticle/796.php for derivation + if freq>=0.5*fs: + raise ValueError("Desired peak frequency is too high") + norm_freq=2*np.pi*freq/fs + numerator=[1,-np.exp(-1j*norm_freq)] + denominator=[1,-2*np.cos(norm_freq),1] + return (numerator,denominator) \ No newline at end of file diff --git a/voicechat_modem_dsp/modulators/modulator_utils.pyi b/voicechat_modem_dsp/modulators/modulator_utils.pyi index 637ef41..c3c7476 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.pyi +++ b/voicechat_modem_dsp/modulators/modulator_utils.pyi @@ -1,6 +1,6 @@ # Stubs for modulators.modulator_utils (Python 3) -from typing import Any, Sequence +from typing import Any, Sequence, Tuple from numpy import ndarray @@ -14,3 +14,6 @@ def fred_harris_fir_tap_count(fs: float, transition_width: float, db_attenuation def lowpass_fir_filter(fs: float, cutoff_low: float, cutoff_high: float, attenuation: float = ...) -> ndarray: ... def linearize_fir(filter: Any) -> None: ... + +def goertzel_iir(freq: float, fs: float) \ + -> Tuple[Sequence[complex],Sequence[complex]]: ... \ No newline at end of file From 47b2f42e5964e96bac50a10d6ba91887df46e7d0 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 1 Jan 2020 17:30:24 -0800 Subject: [PATCH 111/228] Write test to raise ValueError for improper Goertzel parameters --- test/test_modulator_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_modulator_utils.py b/test/test_modulator_utils.py index dcde3b3..11d443c 100644 --- a/test/test_modulator_utils.py +++ b/test/test_modulator_utils.py @@ -22,6 +22,8 @@ def test_filtergen_error(): filt=lowpass_fir_filter(2000,500,100) with pytest.raises(ValueError): filt=lowpass_fir_filter(2000,1000,4000) + with pytest.raises(ValueError): + filt=goertzel_iir(3,2) @pytest.mark.unit def test_unit_average_int(): From fe7de21901884be7b5b0650f16dbebe71f381e97 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 1 Jan 2020 17:30:46 -0800 Subject: [PATCH 112/228] Add docstring for Goertzel IIR method --- voicechat_modem_dsp/modulators/modulator_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 6466537..30b1d28 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -141,8 +141,12 @@ def linearize_fir(fir_filter): # TODO fill in the rest of this code if this ends up being actually used raise NotImplementedError +""" +Helper function to computer the IIR filter for the Goertzel algorithm + +Derivation of formula from https://www.dsprelated.com/showarticle/796.php +""" def goertzel_iir(freq,fs): - # See https://www.dsprelated.com/showarticle/796.php for derivation if freq>=0.5*fs: raise ValueError("Desired peak frequency is too high") norm_freq=2*np.pi*freq/fs From 201011eb70c70dcdc6a57d76162f374d2f7fc1d7 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 1 Jan 2020 18:21:55 -0800 Subject: [PATCH 113/228] Reduce sigma_f for FSKModulator Need a more descriptive name for this sigma --- voicechat_modem_dsp/modulators/modulator_fsk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index 59799c9..d2ed146 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -43,7 +43,7 @@ def _calculate_sigma(self): #sigma_t = w/4k, at most half of the pulse is smoothed away gaussian_sigma_t=(1/self.baud)/(4*Modulator.sigma_mult_t) # TODO: test possibilities that make sense for FSK - gaussian_sigma_f=(2*np.pi/max(self.freq_list.values()))/Modulator.sigma_mult_t + gaussian_sigma_f=0.25*(2*np.pi/max(self.freq_list.values()))/Modulator.sigma_mult_t return min(gaussian_sigma_t,gaussian_sigma_f) def modulate(self, datastream): From 71daecea0921c3e1da3b9a1536d54f9c0cba7d1b Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 1 Jan 2020 18:23:46 -0800 Subject: [PATCH 114/228] Another FSK parameter test --- test/test_modulator_parameters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_modulator_parameters.py b/test/test_modulator_parameters.py index e2c45e4..766f72e 100644 --- a/test/test_modulator_parameters.py +++ b/test/test_modulator_parameters.py @@ -12,11 +12,13 @@ def test_invalid_nyquist(): with pytest.raises(ValueError, match=r"Carrier frequency is too high.+"): bad_modulator=ASKModulator(1000,600,np.linspace(0.2,1,8),20) - with pytest.raises(ValueError, match=r"Baud is too high.+"): bad_modulator=ASKModulator(1000,100,np.linspace(0.2,1,8),80) + with pytest.raises(ValueError, match=r"Baud is too high.+"): bad_modulator=FSKModulator(1000,1,np.linspace(200,1000,8),800) + with pytest.raises(ValueError, match=r"Maximum frequency is too high.+"): + bad_modulator=FSKModulator(1000,0.9,np.linspace(200,600,16),100) with pytest.warns(ModulationIntegrityWarning): bad_modulator=ASKModulator(900,400,np.linspace(0.2,1,8),100) From dbe7c7dcc202e266b1bcb2d5406278911746a006 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 1 Jan 2020 18:24:32 -0800 Subject: [PATCH 115/228] Try to ignore warnings in integrity tests TODO: troubleshoot further (see GitHub issue for details) --- test/test_modulator_integrity.py | 55 +++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/test/test_modulator_integrity.py b/test/test_modulator_integrity.py index d5e78fc..d562c5f 100644 --- a/test/test_modulator_integrity.py +++ b/test/test_modulator_integrity.py @@ -12,9 +12,10 @@ def get_rand_float(lower, upper): return random.random()*(upper-lower)+lower +@pytest.mark.filterwarnings("ignore") @pytest.mark.unit def test_unit_ask_integrity_novoice(): - amplitude_list=list(np.geomspace(0.1,1,16)) + amplitude_list=list(np.linspace(0.1,1,16)) list_data=list(range(256)) bitstream=bytes(list_data) datastream=base_16_encode(bitstream) @@ -30,6 +31,7 @@ def test_unit_ask_integrity_novoice(): assert bitstream==recovered_bitstream +@pytest.mark.filterwarnings("ignore") @pytest.mark.unit def test_unit_ask_integrity_voice(): amplitude_list=list(np.geomspace(0.1,1,16)) @@ -68,6 +70,27 @@ def test_unit_fsk_integrity_bell_202(): assert bitstream==recovered_bitstream +@pytest.mark.filterwarnings("ignore") +@pytest.mark.unit +def test_unit_fsk_integrity_high_pitch(): + frequency_list=[8000,10000,12000,14000] + # Shuffle as opposed to complete random to test all 0x00-0xff + list_data=list(range(256)) + bitstream=bytes(list_data) + datastream=base_4_encode(bitstream) + + sampling_freq=48000 + amplitude=0.5 + baud_rate=4000 + + modulator=FSKModulator(sampling_freq,amplitude,frequency_list,baud_rate) + modulated_data=modulator.modulate(datastream) + demodulated_datastream=modulator.demodulate(modulated_data) + recovered_bitstream=base_4_decode(demodulated_datastream) + + assert bitstream==recovered_bitstream + +@pytest.mark.filterwarnings("ignore") @pytest.mark.property def test_property_ask_integrity(): amplitude_list=list(np.linspace(0.1,1,16)) @@ -97,3 +120,33 @@ def test_property_ask_integrity(): count_run+=1 assert bitstream==recovered_bitstream + +@pytest.mark.filterwarnings("ignore") +@pytest.mark.property +def test_property_fsk_integrity(): + frequency_list=list(np.linspace(512,2048,16)) + count_run=0 + while count_run<256: + # Shuffle as opposed to complete random to test all 0x00-0xff + list_data=list(range(256)) + random.shuffle(list_data) + bitstream=bytes(list_data) + datastream=base_16_encode(bitstream) + + sampling_freq=get_rand_float(8000,48000) + amplitude=get_rand_float(0.1,1) + baud_rate=get_rand_float(64,256) + + with warnings.catch_warnings(): + try: + modulator=FSKModulator(sampling_freq, + amplitude,frequency_list,baud_rate) + except ModulationIntegrityWarning: + continue + + modulated_data=modulator.modulate(datastream) + demodulated_bundle=modulator.demodulate(modulated_data) + recovered_bitstream=base_16_decode(demodulated_bundle) + count_run+=1 + + assert bitstream==recovered_bitstream From 4e94aa97ae61a2f58b1f6047c5c2dda4ce8b0539 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 1 Jan 2020 21:02:35 -0800 Subject: [PATCH 116/228] Create .coveragerc to ignore _base.py files --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..daa431c --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit=*_base.py From 5e77880a721e60b9660ebfd6f7e8d0119adafb83 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 1 Jan 2020 21:02:58 -0800 Subject: [PATCH 117/228] Ignore .wav files from audio testing --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4016d21..fa8f3c7 100644 --- a/.gitignore +++ b/.gitignore @@ -105,5 +105,6 @@ venv.bak/ #Custom additions .vscode/ +*.wav #MyPy stubgen out/ From afd5512de4e873d98e7698062eeb2e8f5f690245 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 2 Jan 2020 11:47:46 -0800 Subject: [PATCH 118/228] Add newline to end of modulator utils [ci skip] --- voicechat_modem_dsp/modulators/modulator_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 30b1d28..c4927f4 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -152,4 +152,5 @@ def goertzel_iir(freq,fs): norm_freq=2*np.pi*freq/fs numerator=[1,-np.exp(-1j*norm_freq)] denominator=[1,-2*np.cos(norm_freq),1] - return (numerator,denominator) \ No newline at end of file + return (numerator,denominator) + From 673bd5f4b1d647b7c960349989b4ff7844813a9a Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 3 Jan 2020 12:31:11 -0800 Subject: [PATCH 119/228] Update FSK Modulator comments before merging --- voicechat_modem_dsp/modulators/modulator_fsk.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index d2ed146..8e72cb2 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -31,6 +31,7 @@ def __init__(self, fs, amplitude, freq_list, baud): if amplitude<0.1: warnings.warn("Amplitude may be too small to allow " "reliable reconstruction",ModulationIntegrityWarning) + # Frequency spacing for DFT dft_min_freq_gap=0.5*fs/(fs/baud) if any(dx<=dft_min_freq_gap for dx in np.diff(sorted(freq_list))): warnings.warn("Frequencies may be too close " @@ -42,6 +43,7 @@ def __init__(self, fs, amplitude, freq_list, baud): def _calculate_sigma(self): #sigma_t = w/4k, at most half of the pulse is smoothed away gaussian_sigma_t=(1/self.baud)/(4*Modulator.sigma_mult_t) + # At most 1/4 of the highest frequency cycle is smoothed away # TODO: test possibilities that make sense for FSK gaussian_sigma_f=0.25*(2*np.pi/max(self.freq_list.values()))/Modulator.sigma_mult_t return min(gaussian_sigma_t,gaussian_sigma_f) @@ -67,7 +69,7 @@ def modulate(self, datastream): shaped_frequency=signal.convolve(interpolated_frequency,gaussian_window, "same",method="fft") - # Construct smoothed amplitude mask + # Construct smoothed frequency mask # TODO: be smarter about only convolving the edges amplitude_mask=np.asarray([0]+[self.amplitude]*len(datastream)+[0]) interpolated_amplitude=modulator_utils.previous_resample_interpolate( @@ -107,7 +109,7 @@ def demodulate(self, modulated_data): interval_begin+=transition_width if i!=interval_count-1: interval_end-=transition_width - # Use np.floor and np.ceil to get integer indexes + # Round transition boundaries to get integer indexes # TODO: find elegant way to handle noninteger interval bounds interval_begin=int(np.round(interval_begin)) interval_end=int(np.round(interval_end)) From 7abf0ba18e2d3212687e4e9f275af1145b679c8b Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 6 Jan 2020 12:26:02 -0800 Subject: [PATCH 120/228] Remove extra newline from end of utils [ci skip] --- voicechat_modem_dsp/modulators/modulator_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index c4927f4..5d8a1de 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -153,4 +153,3 @@ def goertzel_iir(freq,fs): numerator=[1,-np.exp(-1j*norm_freq)] denominator=[1,-2*np.cos(norm_freq),1] return (numerator,denominator) - From 9e0cb41aaf37876257df4cfb318bd4132297c9f4 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 6 Jan 2020 12:58:01 -0800 Subject: [PATCH 121/228] Fix warnings not being caught as errors --- test/test_modulator_integrity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_modulator_integrity.py b/test/test_modulator_integrity.py index d562c5f..db499f7 100644 --- a/test/test_modulator_integrity.py +++ b/test/test_modulator_integrity.py @@ -90,7 +90,6 @@ def test_unit_fsk_integrity_high_pitch(): assert bitstream==recovered_bitstream -@pytest.mark.filterwarnings("ignore") @pytest.mark.property def test_property_ask_integrity(): amplitude_list=list(np.linspace(0.1,1,16)) @@ -103,10 +102,11 @@ def test_property_ask_integrity(): datastream=base_16_encode(bitstream) sampling_freq=get_rand_float(8000,48000) - carrier_freq=get_rand_float(256,sampling_freq/3) + carrier_freq=get_rand_float(256,sampling_freq/2) baud_rate=get_rand_float(128,carrier_freq/4) with warnings.catch_warnings(): + warnings.simplefilter("error",category=ModulationIntegrityWarning) try: modulator=ASKModulator(sampling_freq, carrier_freq,amplitude_list,baud_rate) @@ -121,7 +121,6 @@ def test_property_ask_integrity(): assert bitstream==recovered_bitstream -@pytest.mark.filterwarnings("ignore") @pytest.mark.property def test_property_fsk_integrity(): frequency_list=list(np.linspace(512,2048,16)) @@ -138,6 +137,7 @@ def test_property_fsk_integrity(): baud_rate=get_rand_float(64,256) with warnings.catch_warnings(): + warnings.simplefilter("error",category=ModulationIntegrityWarning) try: modulator=FSKModulator(sampling_freq, amplitude,frequency_list,baud_rate) From eb7309ba4508694dc90030cedd27ee6c599e80d3 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 8 Jan 2020 11:34:21 -0800 Subject: [PATCH 122/228] Continue adjusting spec for config file --- docs/specs/config.rst | 66 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/docs/specs/config.rst b/docs/specs/config.rst index 71dcf68..3b11a5e 100644 --- a/docs/specs/config.rst +++ b/docs/specs/config.rst @@ -5,12 +5,13 @@ The configuration file is written using the `StrictYaml `_ FSK modulation. + +.. code-block:: yaml + + version: 0.1 + fs: 48000 + ecc: none + modulators: + - mode: fsk + amplitude: 1 + frequencies: + - 2200 + - 1200 + baud: 1200 From 21c74a54386877996bbc23755237d7d24f04d391 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 8 Jan 2020 11:34:49 -0800 Subject: [PATCH 123/228] Remove an extraneous newline from ASKModulator --- voicechat_modem_dsp/modulators/modulator_ask.py | 1 - 1 file changed, 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 3e82cfa..e3f9cd2 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -10,7 +10,6 @@ import warnings class ASKModulator(Modulator): - def __init__(self, fs, carrier, amp_list, baud): if baud>=0.5*carrier: raise ValueError("Baud is too high to be modulated "+ From b6ad919ec040d2fcde63c77aac52405cf7621359 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 8 Jan 2020 11:41:19 -0800 Subject: [PATCH 124/228] Write a validator (and tests) for complex number input --- test/test_custom_validators.py | 28 ++++++++++++++++ .../cli/yaml_schema_validators.py | 33 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 test/test_custom_validators.py create mode 100644 voicechat_modem_dsp/cli/yaml_schema_validators.py diff --git a/test/test_custom_validators.py b/test/test_custom_validators.py new file mode 100644 index 0000000..a0cd22b --- /dev/null +++ b/test/test_custom_validators.py @@ -0,0 +1,28 @@ +from voicechat_modem_dsp.cli.yaml_schema_validators import Complex + +from strictyaml import Map, Str, YAMLValidationError, load + +import pytest + +complex_schema=Map({"complex": Complex()}) + +def generate_yaml(val): + return "complex: {}".format(val) + +@pytest.mark.unit +def test_complex_cartesian_valid(): + yaml_str=generate_yaml(1+1j) + yaml_obj=load(yaml_str,schema=complex_schema) + assert yaml_obj["complex"].data==1+1j + yaml_str_2=generate_yaml("(1,0.5)") + yaml_obj_2=load(yaml_str_2,schema=complex_schema) + assert yaml_obj_2["complex"].data.real==-1 + +@pytest.mark.unit +def test_complex_invalid(): + yaml_str_garbage=generate_yaml("garbage") + with pytest.raises(YAMLValidationError): + yaml_obj=load(yaml_str_garbage,schema=complex_schema) + yaml_str_bad_paren=generate_yaml("(aa,bb)") + with pytest.raises(YAMLValidationError): + yaml_obj=load(yaml_str_bad_paren,schema=complex_schema) \ No newline at end of file diff --git a/voicechat_modem_dsp/cli/yaml_schema_validators.py b/voicechat_modem_dsp/cli/yaml_schema_validators.py new file mode 100644 index 0000000..8dc2801 --- /dev/null +++ b/voicechat_modem_dsp/cli/yaml_schema_validators.py @@ -0,0 +1,33 @@ +from strictyaml import ScalarValidator + +import math +#complex_cartesian_regex=r"(\+|-)?[0-9]+(\.[0-9]+)?(\+|-)[0-9]+(\.[0-9]+)?i" +#complex_polar_regex=r"\(\+?[0-9]+(\.[0-9]+)?, ?(\+|-)?[0-9]+(\.[0-9]+)?\)" +#complex_number_regex="({})|({})".format(complex_cartesian_regex, +# complex_polar_regex) + +""" +Validates a complex number input +""" +class Complex(ScalarValidator): + def validate_scalar(self, chunk): + val=chunk.contents + val=val.strip() # whitespace + has_paren=(val[0]=='(' and val[-1]==')') + try: + if has_paren: + val=val[1:-1] + val = val.replace("i","j") + return complex(val) + except ValueError: + if has_paren: + try: + magnitude,angle = tuple(val.split(",")) + magnitude = float(magnitude) + angle = float(angle)*2*math.pi + return magnitude*math.cos(angle)+\ + 1j*magnitude*math.sin(angle) + except ValueError: + chunk.expecting_but_found("when expecting a complex number") + else: + chunk.expecting_but_found("when expecting a complex number") \ No newline at end of file From 12f08b89422e37c9320bd542325a879ef8cc7447 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 9 Jan 2020 13:27:59 -0800 Subject: [PATCH 125/228] Add more example configs and use literalinclude for RST --- docs/specs/ask_1k.yaml | 8 ++++++++ docs/specs/bell_202.yaml | 10 ++++++++++ docs/specs/config.rst | 31 ++++++++++++++----------------- 3 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 docs/specs/ask_1k.yaml create mode 100644 docs/specs/bell_202.yaml diff --git a/docs/specs/ask_1k.yaml b/docs/specs/ask_1k.yaml new file mode 100644 index 0000000..a8611b3 --- /dev/null +++ b/docs/specs/ask_1k.yaml @@ -0,0 +1,8 @@ +version: 0.1 +fs: 48000 +ecc: none +modulators: + - mode: ask + baud: 256 + carrier: 1000 + amplitudes: 0.25, 0.5, 0.75, 1 \ No newline at end of file diff --git a/docs/specs/bell_202.yaml b/docs/specs/bell_202.yaml new file mode 100644 index 0000000..3ead727 --- /dev/null +++ b/docs/specs/bell_202.yaml @@ -0,0 +1,10 @@ +version: 0.1 +fs: 48000 +ecc: none +modulators: + - mode: fsk + baud: 1200 + amplitude: 1 + frequencies: + - 2200 + - 1200 diff --git a/docs/specs/config.rst b/docs/specs/config.rst index 3b11a5e..75e7243 100644 --- a/docs/specs/config.rst +++ b/docs/specs/config.rst @@ -76,20 +76,17 @@ The parameters for the ``fsk`` mode are as follows: - ``amplitude:`` The amplitude of the signal - ``frequencies:`` A list of frequencies between 0 and the Nyquist limit -Example File ------------- - -This is an example config file for `Bell 202 `_ FSK modulation. - -.. code-block:: yaml - - version: 0.1 - fs: 48000 - ecc: none - modulators: - - mode: fsk - amplitude: 1 - frequencies: - - 2200 - - 1200 - baud: 1200 +Example Files +------------- + +`Bell 202 `_ FSK Modulation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: bell_202.yaml + :language: yaml + +1024 bits/second ASK Modulation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: ask_1k.yaml + :language: yaml \ No newline at end of file From a67ab7a55545b22b535f4d155b7a4635f632e276 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 9 Jan 2020 13:46:22 -0800 Subject: [PATCH 126/228] Reorganize tests into subfolders --- test/__init__.py | 0 test/{ => cli}/test_custom_validators.py | 0 test/{ => encoders}/test_bitstream.py | 0 test/{ => encoders}/test_ecc.py | 0 test/{ => encoders}/test_encoders.py | 0 test/{ => modulators}/test_modulator_integrity.py | 0 test/{ => modulators}/test_modulator_parameters.py | 0 test/{ => modulators}/test_modulator_utils.py | 0 8 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/__init__.py rename test/{ => cli}/test_custom_validators.py (100%) rename test/{ => encoders}/test_bitstream.py (100%) rename test/{ => encoders}/test_ecc.py (100%) rename test/{ => encoders}/test_encoders.py (100%) rename test/{ => modulators}/test_modulator_integrity.py (100%) rename test/{ => modulators}/test_modulator_parameters.py (100%) rename test/{ => modulators}/test_modulator_utils.py (100%) diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/test_custom_validators.py b/test/cli/test_custom_validators.py similarity index 100% rename from test/test_custom_validators.py rename to test/cli/test_custom_validators.py diff --git a/test/test_bitstream.py b/test/encoders/test_bitstream.py similarity index 100% rename from test/test_bitstream.py rename to test/encoders/test_bitstream.py diff --git a/test/test_ecc.py b/test/encoders/test_ecc.py similarity index 100% rename from test/test_ecc.py rename to test/encoders/test_ecc.py diff --git a/test/test_encoders.py b/test/encoders/test_encoders.py similarity index 100% rename from test/test_encoders.py rename to test/encoders/test_encoders.py diff --git a/test/test_modulator_integrity.py b/test/modulators/test_modulator_integrity.py similarity index 100% rename from test/test_modulator_integrity.py rename to test/modulators/test_modulator_integrity.py diff --git a/test/test_modulator_parameters.py b/test/modulators/test_modulator_parameters.py similarity index 100% rename from test/test_modulator_parameters.py rename to test/modulators/test_modulator_parameters.py diff --git a/test/test_modulator_utils.py b/test/modulators/test_modulator_utils.py similarity index 100% rename from test/test_modulator_utils.py rename to test/modulators/test_modulator_utils.py From 023ac9d9ce0ca765237f7514e9373c77d296d36e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 9 Jan 2020 14:39:56 -0800 Subject: [PATCH 127/228] Add tests to parse documentation example YAML files --- test/cli/test_yaml_loader.py | 17 ++++++++ voicechat_modem_dsp/cli/config_loader.py | 52 ++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 test/cli/test_yaml_loader.py create mode 100644 voicechat_modem_dsp/cli/config_loader.py diff --git a/test/cli/test_yaml_loader.py b/test/cli/test_yaml_loader.py new file mode 100644 index 0000000..8585b10 --- /dev/null +++ b/test/cli/test_yaml_loader.py @@ -0,0 +1,17 @@ +from voicechat_modem_dsp.cli.config_loader import parse_config_str + +import glob +import os +import pprint + +import pytest + +@pytest.mark.unit +def test_load_doc_examples(): + os.chdir("docs/specs") + for yaml_file in glob.iglob("*.yaml"): + with open(yaml_file,"r") as fil: + config_text=fil.read() + # Just make sure this doesn't cause an error + config_obj=parse_config_str(config_text) + pprint.pprint(config_obj.data) \ No newline at end of file diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py new file mode 100644 index 0000000..d6c9cb3 --- /dev/null +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -0,0 +1,52 @@ +from strictyaml import * + +from .yaml_schema_validators import Complex + +init_config_schema=Map({ + "version": Str(), + "fs": Float(), + "ecc": Enum(["none","raw","hamming_7_4"]), + "modulators": Any() +}) + +ask_schema=Map({ + "baud": Int(), + "mode": Enum(["ask"]), + "carrier": Float(), + "amplitudes": UniqueSeq(Float()) | CommaSeparated(Float()) +}) +psk_schema=Map({ + "baud": Int(), + "mode": Enum(["psk"]), + "carrier": Float(), + "phases": UniqueSeq(Float()) | CommaSeparated(Float()) +}) +qam_schema=Map({ + "baud": Int(), + "mode": Enum(["qam"]), + "carrier": Float(), + "phases": UniqueSeq(Complex()) | CommaSeparated(Complex()) +}) +fsk_schema=Map({ + "baud": Int(), + "mode": Enum(["fsk"]), + "amplitude": Float(), + "frequencies": UniqueSeq(Float()) | CommaSeparated(Float()) +}) + +def parse_config_str(string): + config_obj=load(string,init_config_schema) + # TODO: check version number + if config_obj["fs"].data<=0: + raise ValueError("Sampling frequency must be positive") + for index,modulator in enumerate(config_obj["modulators"]): + for potential_validator in [ask_schema,psk_schema,qam_schema, + fsk_schema]: + try: + modulator.revalidate(potential_validator) + break + except YAMLValidationError: + continue + else: # No validator matched + raise ValueError("Invalid modulator found") + return config_obj From fcf2bab032854a3b76b1a1289853c8066acfa158 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 9 Jan 2020 14:40:28 -0800 Subject: [PATCH 128/228] Add back __init__ files for test folders Strangely necessary with raw pytest but not with coverage run -m pytest --- test/__init__.py | 0 test/cli/__init__.py | 0 test/encoders/__init__.py | 0 test/modulators/__init__.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/__init__.py create mode 100644 test/cli/__init__.py create mode 100644 test/encoders/__init__.py create mode 100644 test/modulators/__init__.py diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/cli/__init__.py b/test/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/encoders/__init__.py b/test/encoders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/modulators/__init__.py b/test/modulators/__init__.py new file mode 100644 index 0000000..e69de29 From 142bbf98e49eab97608d64377be6dc6a4826304e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 9 Jan 2020 17:03:51 -0800 Subject: [PATCH 129/228] Adjust examples and write initial config loader+tests --- docs/specs/config.rst | 4 +-- docs/specs/{ => examples}/ask_1k.yaml | 0 docs/specs/{ => examples}/bell_202.yaml | 0 .../invalid_modulator_in_list.yaml | 8 +++++ docs/specs/nonexamples/negative_fs.yaml | 8 +++++ docs/specs/nonexamples/no_modulator_list.yaml | 4 +++ test/cli/test_yaml_loader.py | 36 ++++++++++++++----- voicechat_modem_dsp/cli/config_loader.py | 11 ++++-- 8 files changed, 59 insertions(+), 12 deletions(-) rename docs/specs/{ => examples}/ask_1k.yaml (100%) rename docs/specs/{ => examples}/bell_202.yaml (100%) create mode 100644 docs/specs/nonexamples/invalid_modulator_in_list.yaml create mode 100644 docs/specs/nonexamples/negative_fs.yaml create mode 100644 docs/specs/nonexamples/no_modulator_list.yaml diff --git a/docs/specs/config.rst b/docs/specs/config.rst index 75e7243..2c5897e 100644 --- a/docs/specs/config.rst +++ b/docs/specs/config.rst @@ -82,11 +82,11 @@ Example Files `Bell 202 `_ FSK Modulation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. literalinclude:: bell_202.yaml +.. literalinclude:: examples/bell_202.yaml :language: yaml 1024 bits/second ASK Modulation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. literalinclude:: ask_1k.yaml +.. literalinclude:: examples/ask_1k.yaml :language: yaml \ No newline at end of file diff --git a/docs/specs/ask_1k.yaml b/docs/specs/examples/ask_1k.yaml similarity index 100% rename from docs/specs/ask_1k.yaml rename to docs/specs/examples/ask_1k.yaml diff --git a/docs/specs/bell_202.yaml b/docs/specs/examples/bell_202.yaml similarity index 100% rename from docs/specs/bell_202.yaml rename to docs/specs/examples/bell_202.yaml diff --git a/docs/specs/nonexamples/invalid_modulator_in_list.yaml b/docs/specs/nonexamples/invalid_modulator_in_list.yaml new file mode 100644 index 0000000..517f4a4 --- /dev/null +++ b/docs/specs/nonexamples/invalid_modulator_in_list.yaml @@ -0,0 +1,8 @@ +version: 0.1 +fs: 4000 +ecc: none +modulators: + - mode: triaxilated + baud: 2400 + carrier: unknown + triangulation: unsuccessful \ No newline at end of file diff --git a/docs/specs/nonexamples/negative_fs.yaml b/docs/specs/nonexamples/negative_fs.yaml new file mode 100644 index 0000000..bad948c --- /dev/null +++ b/docs/specs/nonexamples/negative_fs.yaml @@ -0,0 +1,8 @@ +version: 0.1 +fs: -4000 +ecc: none +modulators: + - mode: ask + baud: 256 + carrier: 1000 + amplitudes: 0.25, 0.5, 0.75, 1 \ No newline at end of file diff --git a/docs/specs/nonexamples/no_modulator_list.yaml b/docs/specs/nonexamples/no_modulator_list.yaml new file mode 100644 index 0000000..9ba3765 --- /dev/null +++ b/docs/specs/nonexamples/no_modulator_list.yaml @@ -0,0 +1,4 @@ +version: 0.1 +fs: 4000 +ecc: none +modulators: "This is not the file you are looking for" \ No newline at end of file diff --git a/test/cli/test_yaml_loader.py b/test/cli/test_yaml_loader.py index 8585b10..70be32b 100644 --- a/test/cli/test_yaml_loader.py +++ b/test/cli/test_yaml_loader.py @@ -2,16 +2,36 @@ import glob import os -import pprint +from strictyaml import YAMLValidationError import pytest +class DirectoryChanger: + def __init__(self, path): + self.oldpath=os.getcwd() + self.path=path + + def __enter__(self): + os.chdir(self.path) + def __exit__(self, exc_type, exc_val, exc_tb): + os.chdir(self.oldpath) + @pytest.mark.unit def test_load_doc_examples(): - os.chdir("docs/specs") - for yaml_file in glob.iglob("*.yaml"): - with open(yaml_file,"r") as fil: - config_text=fil.read() - # Just make sure this doesn't cause an error - config_obj=parse_config_str(config_text) - pprint.pprint(config_obj.data) \ No newline at end of file + with DirectoryChanger("docs/specs/examples"): + for yaml_file in glob.iglob("*.yaml"): + with open(yaml_file,"r") as fil: + config_text=fil.read() + # Just make sure this doesn't cause an error + config_obj=parse_config_str(config_text) + +@pytest.mark.unit +def test_load_doc_nonexamples(): + with DirectoryChanger("docs/specs/nonexamples"): + for yaml_file in glob.iglob("*.yaml"): + with open(yaml_file,"r") as fil: + config_text=fil.read() + # Just make sure this causes an error + # See TODO notice as to why not check error message contents + with pytest.raises(YAMLValidationError): + config_obj=parse_config_str(config_text) \ No newline at end of file diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index d6c9cb3..3cbecec 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -34,11 +34,17 @@ "frequencies": UniqueSeq(Float()) | CommaSeparated(Float()) }) +# TODO: more informative ValidationError messages +# This is partially due to underdocumented libraries def parse_config_str(string): config_obj=load(string,init_config_schema) # TODO: check version number if config_obj["fs"].data<=0: - raise ValueError("Sampling frequency must be positive") + raise YAMLValidationError("Sampling frequency must be positive", + None,config_obj["fs"]) + if not config_obj["modulators"].is_sequence(): + raise YAMLValidationError("Modulators must be a list of modulators", + None,config_obj["modulators"]) for index,modulator in enumerate(config_obj["modulators"]): for potential_validator in [ask_schema,psk_schema,qam_schema, fsk_schema]: @@ -48,5 +54,6 @@ def parse_config_str(string): except YAMLValidationError: continue else: # No validator matched - raise ValueError("Invalid modulator found") + raise YAMLValidationError("Invalid modulator found " + "in modulator list",None,modulator) return config_obj From 090ac81d4dde0fd85e3c8aaafb4c36102ce2fc38 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 9 Jan 2020 22:44:52 -0800 Subject: [PATCH 130/228] Remove Gaussian smoothing before phase integration Phase integration already ensures continuous output signal Gaussian smoothing phase also causes frequency smearing and reduce signal integrity --- voicechat_modem_dsp/modulators/modulator_fsk.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index 8e72cb2..1d37ee0 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -65,9 +65,6 @@ def modulate(self, datastream): self.fs,interp_sample_count) interpolated_frequency=modulator_utils.previous_resample_interpolate( time_array, self.baud, frequency_data) - # Smooth frequencies with Gaussian kernel - shaped_frequency=signal.convolve(interpolated_frequency,gaussian_window, - "same",method="fft") # Construct smoothed frequency mask # TODO: be smarter about only convolving the edges @@ -79,7 +76,7 @@ def modulate(self, datastream): # Frequency is the derivative of phase phase_array=scipy.integrate.cumtrapz( - shaped_frequency,time_array,initial=0)%1 + interpolated_frequency,time_array,initial=0) phase_array*=2*np.pi return shaped_amplitude*np.cos(phase_array) From 13d3117ddd346ad6162529aaab5da9161c682284 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 9 Jan 2020 22:48:13 -0800 Subject: [PATCH 131/228] Write initial notebook describing FSK modulation Fill in details momentarily --- docs/notebooks/FSK Modulator.ipynb | 138 +++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 docs/notebooks/FSK Modulator.ipynb diff --git a/docs/notebooks/FSK Modulator.ipynb b/docs/notebooks/FSK Modulator.ipynb new file mode 100644 index 0000000..48f0bc5 --- /dev/null +++ b/docs/notebooks/FSK Modulator.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# FSK Modulation and Demodulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "FSK modulation varies the frequency of a signal to modulate information, and can be demodulated using techniques such as a Goertzel filter. FSK is insensitive to non-frequency-dependent amplitude variations." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from scipy import signal" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use coherent FSK modulation to prevent discontinuities when the frequency changes. To accomplish this, we view frequency as the derivative of phase, first constructing the list of frequencies, and then integrating it over time to get the instantaneous phase." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# Plot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also do amplitude shaping at the ends by padding the ends with a 0-frequency dummy symbol and performing Gaussian smoothing on the amplitude to prevent discontinuity at the ends of the modulated signal." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Demodulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To perform demodulation, we construct a Goertzel filter, tuned to the desired frequency. The form of the Goertzel filter (taken from https://www.dsprelated.com/showarticle/796.php) is\n", + "\n", + "\\begin{align*}\n", + "H_G(z) &= \\frac{1}{1-e^{2\\pi jf}z^{-1}} \\cdot \\frac{1-e^{-2\\pi jf}z^{-1}}{1-e^{-2\\pi jf}z^{-1}} \\\\\n", + "&= \\frac{1-e^{-2\\pi jf}z^{-1}}{1-2\\cos(2\\pi f)z^{-1}+z^{-2}}\n", + "\\end{align*}\n", + "\n", + "We use this form (which has complex-conjugate poles and a zero to cancel out the bottom pole) to improve numerical stability, as explained in the linked article above." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def goertzel_iir(freq,fs):\n", + " if freq>=0.5*fs:\n", + " raise ValueError(\"Desired peak frequency is too high\")\n", + " norm_freq=2*np.pi*freq/fs\n", + " numerator=[1,-np.exp(-1j*norm_freq)]\n", + " denominator=[1,-2*np.cos(norm_freq),1]\n", + " return (numerator,denominator)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to perform the demodulation, we divide the modulated signal into symbol blocks, filter each block through Goertzel filters tuned to each frequency, and compare the absolute values of the last output of each filter." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Compute and plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Environment (virtualenv_voicechat-modem-dsp-ciksczpw)", + "language": "python", + "name": "virtualenv_voicechat-modem-dsp-ciksczpw" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 09e6d0fcde3972435b9fc3f07e37e852bf87aa54 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 10 Jan 2020 15:37:22 -0800 Subject: [PATCH 132/228] Install matplotlib for graphs in modulation notebooks --- Pipfile | 1 + Pipfile.lock | 281 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 179 insertions(+), 103 deletions(-) diff --git a/Pipfile b/Pipfile index aa2e94c..e9898d6 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ protobuf = "*" ipykernel = "*" cleo = "*" strictyaml = "*" +matplotlib = "*" [dev-packages] pytest = "*" diff --git a/Pipfile.lock b/Pipfile.lock index cabff5d..e26d93e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "efd4a81e6dc69ab8299984822a50abf0c6b9527500539faaf952d45d1bb977c5" + "sha256": "d5de3bdc80a19f796d5b0b8133b18f5601e236425a8606df586623f4a7f748fe" }, "pipfile-spec": 6, "requires": {}, @@ -36,6 +36,13 @@ ], "version": "==0.4.1" }, + "cycler": { + "hashes": [ + "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", + "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8" + ], + "version": "==0.10.0" + }, "decorator": { "hashes": [ "sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", @@ -53,10 +60,10 @@ }, "ipython": { "hashes": [ - "sha256:190a279bd3d4fc585a611e9358a88f1048cc57fd688254a86f9461889ee152a6", - "sha256:762d79a62b6aa96b04971e920543f558dfbeedc0468b899303c080c8068d4ac2" + "sha256:0f4bcf18293fb666df8511feec0403bdb7e061a5842ea6e88a3177b0ceb34ead", + "sha256:387686dd7fc9caf29d2fddcf3116c4b07a11d9025701d220c589a430b0171d8a" ], - "version": "==7.10.2" + "version": "==7.11.1" }, "ipython-genutils": { "hashes": [ @@ -86,32 +93,93 @@ ], "version": "==4.6.1" }, + "kiwisolver": { + "hashes": [ + "sha256:05b5b061e09f60f56244adc885c4a7867da25ca387376b02c1efc29cc16bcd0f", + "sha256:210d8c39d01758d76c2b9a693567e1657ec661229bc32eac30761fa79b2474b0", + "sha256:26f4fbd6f5e1dabff70a9ba0d2c4bd30761086454aa30dddc5b52764ee4852b7", + "sha256:3b15d56a9cd40c52d7ab763ff0bc700edbb4e1a298dc43715ecccd605002cf11", + "sha256:3b2378ad387f49cbb328205bda569b9f87288d6bc1bf4cd683c34523a2341efe", + "sha256:400599c0fe58d21522cae0e8b22318e09d9729451b17ee61ba8e1e7c0346565c", + "sha256:47b8cb81a7d18dbaf4fed6a61c3cecdb5adec7b4ac292bddb0d016d57e8507d5", + "sha256:53eaed412477c836e1b9522c19858a8557d6e595077830146182225613b11a75", + "sha256:58e626e1f7dfbb620d08d457325a4cdac65d1809680009f46bf41eaf74ad0187", + "sha256:5a52e1b006bfa5be04fe4debbcdd2688432a9af4b207a3f429c74ad625022641", + "sha256:5c7ca4e449ac9f99b3b9d4693debb1d6d237d1542dd6a56b3305fe8a9620f883", + "sha256:682e54f0ce8f45981878756d7203fd01e188cc6c8b2c5e2cf03675390b4534d5", + "sha256:76275ee077772c8dde04fb6c5bc24b91af1bb3e7f4816fd1852f1495a64dad93", + "sha256:79bfb2f0bd7cbf9ea256612c9523367e5ec51d7cd616ae20ca2c90f575d839a2", + "sha256:7f4dd50874177d2bb060d74769210f3bce1af87a8c7cf5b37d032ebf94f0aca3", + "sha256:8944a16020c07b682df861207b7e0efcd2f46c7488619cb55f65882279119389", + "sha256:8aa7009437640beb2768bfd06da049bad0df85f47ff18426261acecd1cf00897", + "sha256:9105ce82dcc32c73eb53a04c869b6a4bc756b43e4385f76ea7943e827f529e4d", + "sha256:933df612c453928f1c6faa9236161a1d999a26cd40abf1dc5d7ebbc6dbfb8fca", + "sha256:939f36f21a8c571686eb491acfffa9c7f1ac345087281b412d63ea39ca14ec4a", + "sha256:9491578147849b93e70d7c1d23cb1229458f71fc79c51d52dce0809b2ca44eea", + "sha256:9733b7f64bd9f807832d673355f79703f81f0b3e52bfce420fc00d8cb28c6a6c", + "sha256:a02f6c3e229d0b7220bd74600e9351e18bc0c361b05f29adae0d10599ae0e326", + "sha256:a0c0a9f06872330d0dd31b45607197caab3c22777600e88031bfe66799e70bb0", + "sha256:aa716b9122307c50686356cfb47bfbc66541868078d0c801341df31dca1232a9", + "sha256:acc4df99308111585121db217681f1ce0eecb48d3a828a2f9bbf9773f4937e9e", + "sha256:b64916959e4ae0ac78af7c3e8cef4becee0c0e9694ad477b4c6b3a536de6a544", + "sha256:d22702cadb86b6fcba0e6b907d9f84a312db9cd6934ee728144ce3018e715ee1", + "sha256:d3fcf0819dc3fea58be1fd1ca390851bdb719a549850e708ed858503ff25d995", + "sha256:d52e3b1868a4e8fd18b5cb15055c76820df514e26aa84cc02f593d99fef6707f", + "sha256:db1a5d3cc4ae943d674718d6c47d2d82488ddd94b93b9e12d24aabdbfe48caee", + "sha256:e3a21a720791712ed721c7b95d433e036134de6f18c77dbe96119eaf7aa08004", + "sha256:e8bf074363ce2babeb4764d94f8e65efd22e6a7c74860a4f05a6947afc020ff2", + "sha256:f16814a4a96dc04bf1da7d53ee8d5b1d6decfc1a92a63349bb15d37b6a263dd9", + "sha256:f2b22153870ca5cf2ab9c940d7bc38e8e9089fa0f7e5856ea195e1cf4ff43d5a", + "sha256:f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f", + "sha256:fe51b79da0062f8e9d49ed0182a626a7dc7a0cbca0328f612c6ee5e4711c81e4" + ], + "version": "==1.1.0" + }, + "matplotlib": { + "hashes": [ + "sha256:08ccc8922eb4792b91c652d3e6d46b1c99073f1284d1b6705155643e8046463a", + "sha256:161dcd807c0c3232f4dcd4a12a382d52004a498174cbfafd40646106c5bcdcc8", + "sha256:1f9e885bfa1b148d16f82a6672d043ecf11197f6c71ae222d0546db706e52eb2", + "sha256:2d6ab54015a7c0d727c33e36f85f5c5e4172059efdd067f7527f6e5d16ad01aa", + "sha256:5d2e408a2813abf664bd79431107543ecb449136912eb55bb312317edecf597e", + "sha256:61c8b740a008218eb604de518eb411c4953db0cb725dd0b32adf8a81771cab9e", + "sha256:80f10af8378fccc136da40ea6aa4a920767476cdfb3241acb93ef4f0465dbf57", + "sha256:819d4860315468b482f38f1afe45a5437f60f03eaede495d5ff89f2eeac89500", + "sha256:8cc0e44905c2c8fda5637cad6f311eb9517017515a034247ab93d0cf99f8bb7a", + "sha256:8e8e2c2fe3d873108735c6ee9884e6f36f467df4a143136209cff303b183bada", + "sha256:98c2ffeab8b79a4e3a0af5dd9939f92980eb6e3fec10f7f313df5f35a84dacab", + "sha256:d59bb0e82002ac49f4152963f8a1079e66794a4f454457fd2f0dcc7bf0797d30", + "sha256:ee59b7bb9eb75932fe3787e54e61c99b628155b0cedc907864f24723ba55b309" + ], + "index": "pypi", + "version": "==3.1.2" + }, "numpy": { "hashes": [ - "sha256:03bbde29ac8fba860bb2c53a1525b3604a9b60417855ac3119d89868ec6041c3", - "sha256:1baefd1fb4695e7f2e305467dbd876d765e6edd30c522894df76f8301efaee36", - "sha256:1c35fb1131362e6090d30286cfda52ddd42e69d3e2bf1fea190a0fad83ea3a18", - "sha256:3c68c827689ca0ca713dba598335073ce0966850ec0b30715527dce4ecd84055", - "sha256:443ab93fc35b31f01db8704681eb2fd82f3a1b2fa08eed2dd0e71f1f57423d4a", - "sha256:56710a756c5009af9f35b91a22790701420406d9ac24cf6b652b0e22cfbbb7ff", - "sha256:62506e9e4d2a39c87984f081a2651d4282a1d706b1a82fe9d50a559bb58e705a", - "sha256:6f8113c8dbfc192b58996ee77333696469ea121d1c44ea429d8fd266e4c6be51", - "sha256:712f0c32555132f4b641b918bdb1fd3c692909ae916a233ce7f50eac2de87e37", - "sha256:854f6ed4fa91fa6da5d764558804ba5b0f43a51e5fe9fc4fdc93270b052f188a", - "sha256:88c5ccbc4cadf39f32193a5ef22e3f84674418a9fd877c63322917ae8f295a56", - "sha256:905cd6fa6ac14654a6a32b21fad34670e97881d832e24a3ca32e19b455edb4a8", - "sha256:9d6de2ad782aae68f7ed0e0e616477fbf693d6d7cc5f0f1505833ff12f84a673", - "sha256:a30f5c3e1b1b5d16ec1f03f4df28e08b8a7529d8c920bbed657f4fde61f1fbcd", - "sha256:a9d72d9abaf65628f0f31bbb573b7d9304e43b1e6bbae43149c17737a42764c4", - "sha256:ac3cf835c334fcc6b74dc4e630f9b5ff7b4c43f7fb2a7813208d95d4e10b5623", - "sha256:b091e5d4cbbe79f0e8b6b6b522346e54a282eadb06e3fd761e9b6fafc2ca91ad", - "sha256:cc070fc43a494e42732d6ae2f6621db040611c1dde64762a40c8418023af56d7", - "sha256:e1080e37c090534adb2dd7ae1c59ee883e5d8c3e63d2a4d43c20ee348d0459c5", - "sha256:f084d513de729ff10cd72a1f80db468cff464fedb1ef2fea030221a0f62d7ff4", - "sha256:f6a7421da632fc01e8a3ecd19c3f7350258d82501a646747664bae9c6a87c731" + "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", + "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", + "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", + "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", + "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", + "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", + "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", + "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", + "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", + "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", + "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", + "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", + "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", + "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", + "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", + "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", + "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", + "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", + "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", + "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", + "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5" ], "index": "pypi", - "version": "==1.18.0" + "version": "==1.18.1" }, "parso": { "hashes": [ @@ -195,6 +263,13 @@ ], "version": "==1.3.0" }, + "pyparsing": { + "hashes": [ + "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", + "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" + ], + "version": "==2.4.6" + }, "python-dateutil": { "hashes": [ "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", @@ -329,10 +404,10 @@ }, "wcwidth": { "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603", + "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8" ], - "version": "==0.1.7" + "version": "==0.1.8" } }, "develop": { @@ -352,10 +427,10 @@ }, "babel": { "hashes": [ - "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", - "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" + "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", + "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4" ], - "version": "==2.7.0" + "version": "==2.8.0" }, "bleach": { "hashes": [ @@ -380,40 +455,40 @@ }, "coverage": { "hashes": [ - "sha256:0101888bd1592a20ccadae081ba10e8b204d20235d18d05c6f7d5e904a38fc10", - "sha256:04b961862334687549eb91cd5178a6fbe977ad365bddc7c60f2227f2f9880cf4", - "sha256:1ca43dbd739c0fc30b0a3637a003a0d2c7edc1dd618359d58cc1e211742f8bd1", - "sha256:1cbb88b34187bdb841f2599770b7e6ff8e259dc3bb64fc7893acf44998acf5f8", - "sha256:232f0b52a5b978288f0bbc282a6c03fe48cd19a04202df44309919c142b3bb9c", - "sha256:24bcfa86fd9ce86b73a8368383c39d919c497a06eebb888b6f0c12f13e920b1a", - "sha256:25b8f60b5c7da71e64c18888f3067d5b6f1334b9681876b2fb41eea26de881ae", - "sha256:2714160a63da18aed9340c70ed514973971ee7e665e6b336917ff4cca81a25b1", - "sha256:2ca2cd5264e84b2cafc73f0045437f70c6378c0d7dbcddc9ee3fe192c1e29e5d", - "sha256:2cc707fc9aad2592fc686d63ef72dc0031fc98b6fb921d2f5395d9ab84fbc3ef", - "sha256:348630edea485f4228233c2f310a598abf8afa5f8c716c02a9698089687b6085", - "sha256:40fbfd6b044c9db13aeec1daf5887d322c710d811f944011757526ef6e323fd9", - "sha256:46c9c6a1d1190c0b75ec7c0f339088309952b82ae8d67a79ff1319eb4e749b96", - "sha256:591506e088901bdc25620c37aec885e82cc896528f28c57e113751e3471fc314", - "sha256:5ac71bba1e07eab403b082c4428f868c1c9e26a21041436b4905c4c3d4e49b08", - "sha256:5f622f19abda4e934938e24f1d67599249abc201844933a6f01aaa8663094489", - "sha256:65bead1ac8c8930cf92a1ccaedcce19a57298547d5d1db5c9d4d068a0675c38b", - "sha256:7362a7f829feda10c7265b553455de596b83d1623b3d436b6d3c51c688c57bf6", - "sha256:7f2675750c50151f806070ec11258edf4c328340916c53bac0adbc465abd6b1e", - "sha256:960d7f42277391e8b1c0b0ae427a214e1b31a1278de6b73f8807b20c2e913bba", - "sha256:a50b0888d8a021a3342d36a6086501e30de7d840ab68fca44913e97d14487dc1", - "sha256:b7dbc5e8c39ea3ad3db22715f1b5401cd698a621218680c6daf42c2f9d36e205", - "sha256:bb3d29df5d07d5399d58a394d0ef50adf303ab4fbf66dfd25b9ef258effcb692", - "sha256:c0fff2733f7c2950f58a4fd09b5db257b00c6fec57bf3f68c5bae004d804b407", - "sha256:c792d3707a86c01c02607ae74364854220fb3e82735f631cd0a345dea6b4cee5", - "sha256:c90bda74e16bcd03861b09b1d37c0a4158feda5d5a036bb2d6e58de6ff65793e", - "sha256:cfce79ce41cc1a1dc7fc85bb41eeeb32d34a4cf39a645c717c0550287e30ff06", - "sha256:eeafb646f374988c22c8e6da5ab9fb81367ecfe81c70c292623373d2a021b1a1", - "sha256:f425f50a6dd807cb9043d15a4fcfba3b5874a54d9587ccbb748899f70dc18c47", - "sha256:fcd4459fe35a400b8f416bc57906862693c9f88b66dc925e7f2a933e77f6b18b", - "sha256:ff3936dd5feaefb4f91c8c1f50a06c588b5dc69fba4f7d9c79a6617ad80bb7df" + "sha256:189aac76d6e0d7af15572c51892e7326ee451c076c5a50a9d266406cd6c49708", + "sha256:1bf7ba2af1d373a1750888724f84cffdfc697738f29a353c98195f98fc011509", + "sha256:1f4ee8e2e4243971618bc16fcc4478317405205f135e95226c2496e2a3b8dbbf", + "sha256:225e79a5d485bc1642cb7ba02281419c633c216cdc6b26c26494ba959f09e69f", + "sha256:23688ff75adfa8bfa2a67254d889f9bdf9302c27241d746e17547c42c732d3f4", + "sha256:28f7f73b34a05e23758e860a89a7f649b85c6749e252eff60ebb05532d180e86", + "sha256:2d0cb9b1fe6ad0d915d45ad3d87f03a38e979093a98597e755930db1f897afae", + "sha256:47874b4711c5aeb295c31b228a758ce3d096be83dc37bd56da48ed99efb8813b", + "sha256:511ec0c00840e12fb4e852e4db58fa6a01ca4da72f36a9766fae344c3d502033", + "sha256:53e7438fef0c97bc248f88ba1edd10268cd94d5609970aaf87abbe493691af87", + "sha256:569f9ee3025682afda6e9b0f5bb14897c0db03f1a1dc088b083dd36e743f92bb", + "sha256:593853aa1ac6dcc6405324d877544c596c9d948ef20d2e9512a0f5d2d3202356", + "sha256:5b0a07158360d22492f9abd02a0f2ee7981b33f0646bf796598b7673f6bbab14", + "sha256:7ca3db38a61f3655a2613ee2c190d63639215a7a736d3c64cc7bbdb002ce6310", + "sha256:7d1cc7acc9ce55179616cf72154f9e648136ea55987edf84addbcd9886ffeba2", + "sha256:88b51153657612aea68fa684a5b88037597925260392b7bb4509d4f9b0bdd889", + "sha256:955ec084f549128fa2702f0b2dc696392001d986b71acd8fd47424f28289a9c3", + "sha256:b251c7092cbb6d789d62dc9c9e7c4fb448c9138b51285c36aeb72462cad3600e", + "sha256:bd82b684bb498c60ef47bb1541a50e6d006dde8579934dcbdbc61d67d1ea70d9", + "sha256:bfe102659e2ec13b86c7f3b1db6c9a4e7beea4255058d006351339e6b342d5d2", + "sha256:c1e4e39e43057396a5e9d069bfbb6ffeee892e40c5d2effbd8cd71f34ee66c4d", + "sha256:cb2b74c123f65e8166f7e1265829a6c8ed755c3cd16d7f50e75a83456a5f3fd7", + "sha256:cca38ded59105f7705ef6ffe1e960b8db6c7d8279c1e71654a4775ab4454ca15", + "sha256:cf908840896f7aa62d0ec693beb53264b154f972eb8226fb864ac38975590c4f", + "sha256:d095a7b473f8a95f7efe821f92058c8a2ecfb18f8db6677ae3819e15dc11aaae", + "sha256:d22b4297e7e4225ccf01f1aa55e7a96412ea0796b532dd614c3fcbafa341128e", + "sha256:d4a2b578a7a70e0c71f662705262f87a456f1e6c1e40ada7ea699abaf070a76d", + "sha256:ddeb42a3d5419434742bf4cc71c9eaa22df3b76808e23a82bd0b0bd360f1a9f1", + "sha256:e65a5aa1670db6263f19fdc03daee1d7dbbadb5cb67fd0a1f16033659db13c1d", + "sha256:eaad65bd20955131bcdb3967a4dea66b4e4d4ca488efed7c00d91ee0173387e8", + "sha256:f45fba420b94165c17896861bb0e8b27fb7abdcedfeb154895d8553df90b7b00" ], "index": "pypi", - "version": "==5.0.1" + "version": "==5.0.2" }, "decorator": { "hashes": [ @@ -475,11 +550,11 @@ }, "itikz": { "hashes": [ - "sha256:11e536bb06fd298edd431e58a75e14ee3abdb4bee30352d3301edf8ca0532f50", - "sha256:eea45c5b1ee1c0359a936a2a3a535e8ac19404b08fa2dbb35a3a860e3a1c1e4f" + "sha256:7b789bfa326e64735306bd019a3b2c1c2caf273fc5f1762d0e371c992e5b2376", + "sha256:d258e2a0f9434deea6fbca9bd033e87dd73944975d4816e8d3b803d2cfd2b599" ], "index": "pypi", - "version": "==0.1.4" + "version": "==0.1.5" }, "jinja2": { "hashes": [ @@ -586,10 +661,10 @@ }, "nbformat": { "hashes": [ - "sha256:b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b", - "sha256:f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402" + "sha256:cca9a1acfd4e049dcd6c3628d3c84db8e48a770182fb7b87d6a62f9ceacfae39", + "sha256:d1407544cf0c53ee88f504b6c732aef6e0f407a0858b405fcf133e0a25bb787b" ], - "version": "==4.4.0" + "version": "==5.0.3" }, "nbsphinx": { "hashes": [ @@ -601,30 +676,30 @@ }, "numpy": { "hashes": [ - "sha256:03bbde29ac8fba860bb2c53a1525b3604a9b60417855ac3119d89868ec6041c3", - "sha256:1baefd1fb4695e7f2e305467dbd876d765e6edd30c522894df76f8301efaee36", - "sha256:1c35fb1131362e6090d30286cfda52ddd42e69d3e2bf1fea190a0fad83ea3a18", - "sha256:3c68c827689ca0ca713dba598335073ce0966850ec0b30715527dce4ecd84055", - "sha256:443ab93fc35b31f01db8704681eb2fd82f3a1b2fa08eed2dd0e71f1f57423d4a", - "sha256:56710a756c5009af9f35b91a22790701420406d9ac24cf6b652b0e22cfbbb7ff", - "sha256:62506e9e4d2a39c87984f081a2651d4282a1d706b1a82fe9d50a559bb58e705a", - "sha256:6f8113c8dbfc192b58996ee77333696469ea121d1c44ea429d8fd266e4c6be51", - "sha256:712f0c32555132f4b641b918bdb1fd3c692909ae916a233ce7f50eac2de87e37", - "sha256:854f6ed4fa91fa6da5d764558804ba5b0f43a51e5fe9fc4fdc93270b052f188a", - "sha256:88c5ccbc4cadf39f32193a5ef22e3f84674418a9fd877c63322917ae8f295a56", - "sha256:905cd6fa6ac14654a6a32b21fad34670e97881d832e24a3ca32e19b455edb4a8", - "sha256:9d6de2ad782aae68f7ed0e0e616477fbf693d6d7cc5f0f1505833ff12f84a673", - "sha256:a30f5c3e1b1b5d16ec1f03f4df28e08b8a7529d8c920bbed657f4fde61f1fbcd", - "sha256:a9d72d9abaf65628f0f31bbb573b7d9304e43b1e6bbae43149c17737a42764c4", - "sha256:ac3cf835c334fcc6b74dc4e630f9b5ff7b4c43f7fb2a7813208d95d4e10b5623", - "sha256:b091e5d4cbbe79f0e8b6b6b522346e54a282eadb06e3fd761e9b6fafc2ca91ad", - "sha256:cc070fc43a494e42732d6ae2f6621db040611c1dde64762a40c8418023af56d7", - "sha256:e1080e37c090534adb2dd7ae1c59ee883e5d8c3e63d2a4d43c20ee348d0459c5", - "sha256:f084d513de729ff10cd72a1f80db468cff464fedb1ef2fea030221a0f62d7ff4", - "sha256:f6a7421da632fc01e8a3ecd19c3f7350258d82501a646747664bae9c6a87c731" + "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", + "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", + "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", + "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", + "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", + "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", + "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", + "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", + "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", + "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", + "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", + "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", + "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", + "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", + "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", + "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", + "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", + "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", + "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", + "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", + "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5" ], "index": "pypi", - "version": "==1.18.0" + "version": "==1.18.1" }, "numpy-stubs": { "editable": true, @@ -632,10 +707,10 @@ }, "packaging": { "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" + "sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb", + "sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8" ], - "version": "==19.2" + "version": "==20.0" }, "pandocfilters": { "hashes": [ @@ -652,10 +727,10 @@ }, "py": { "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", + "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" ], - "version": "==1.8.0" + "version": "==1.8.1" }, "pygments": { "hashes": [ @@ -673,9 +748,9 @@ }, "pyrsistent": { "hashes": [ - "sha256:f3b280d030afb652f79d67c5586157c5c1355c9a58dfc7940566e28d28f3df1b" + "sha256:cdc7b5e3ed77bed61270a47d35434a30617b9becdf2478af76ad2c6ade307280" ], - "version": "==0.15.6" + "version": "==0.15.7" }, "pytest": { "hashes": [ @@ -819,10 +894,10 @@ }, "wcwidth": { "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603", + "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8" ], - "version": "==0.1.7" + "version": "==0.1.8" }, "webencodings": { "hashes": [ From a921a1f396d52171436fde55d24cfd538fbc9b7d Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 10 Jan 2020 15:37:42 -0800 Subject: [PATCH 133/228] Finish writing FSK Modulator notebook --- docs/index.rst | 1 + docs/notebooks/FSK Modulator.ipynb | 3626 +++++++++++++++++++++++++++- 2 files changed, 3615 insertions(+), 12 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index ebda697..79c39ab 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,6 +11,7 @@ Welcome to Voicechat Modem (DSP Component)'s documentation! :caption: Contents: notebooks/Pre-Modulation Gaussian Smoothing + notebooks/FSK Modulator specs/config Indices and tables diff --git a/docs/notebooks/FSK Modulator.ipynb b/docs/notebooks/FSK Modulator.ipynb index 48f0bc5..3a34464 100644 --- a/docs/notebooks/FSK Modulator.ipynb +++ b/docs/notebooks/FSK Modulator.ipynb @@ -21,7 +21,21 @@ "outputs": [], "source": [ "import numpy as np\n", - "from scipy import signal" + "from scipy import signal\n", + "from scipy.integrate import cumtrapz\n", + "\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import set_matplotlib_formats\n", + "%matplotlib inline\n", + "set_matplotlib_formats('svg')" ] }, { @@ -40,11 +54,992 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ - "# Plot" + "timesequence=np.linspace(0,4,4001)\n", + "fs=1/(timesequence[1]-timesequence[0])\n", + "frequencies=4*np.heaviside(timesequence-2,0.5)+2\n", + "phases=cumtrapz(frequencies,timesequence,initial=0)\n", + "phases*=2*np.pi\n", + "output_signal=np.cos(phases)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(timesequence,np.cos(phases))\n", + "plt.show()" ] }, { @@ -77,7 +1072,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -90,28 +1085,2635 @@ " return (numerator,denominator)" ] }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "goertzel_two=goertzel_iir(2,fs)\n", + "goertzel_four=goertzel_iir(4,fs)\n", + "goertzel_six=goertzel_iir(6,fs)" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In order to perform the demodulation, we divide the modulated signal into symbol blocks, filter each block through Goertzel filters tuned to each frequency, and compare the absolute values of the last output of each filter." + "We plot the frequency responses of the filters:" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ - "# Compute and plot" + "freq_sample=np.linspace(0, 10, 512, endpoint=False)\n", + "freq_two,resp_two=signal.freqz(*goertzel_two,worN=freq_sample,fs=fs)\n", + "freq_four,resp_four=signal.freqz(*goertzel_four,worN=freq_sample,fs=fs)\n", + "freq_six,resp_six=signal.freqz(*goertzel_six,worN=freq_sample,fs=fs)\n", + "plt.semilogy(freq_two,np.abs(resp_two))\n", + "plt.semilogy(freq_four,np.abs(resp_four))\n", + "plt.semilogy(freq_six,np.abs(resp_six))\n", + "plt.legend([\"f=2\",\"f=4\",\"f=6\"])\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to perform the demodulation, we divide the modulated signal into symbol blocks, filter each block through Goertzel filters tuned to each frequency, and compare the absolute values of the last output of each filter." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(timesequence,np.abs(signal.lfilter(*goertzel_two,output_signal)))\n", + "plt.plot(timesequence,np.abs(signal.lfilter(*goertzel_four,output_signal)))\n", + "plt.plot(timesequence,np.abs(signal.lfilter(*goertzel_six,output_signal)))\n", + "plt.legend([\"f=2\",\"f=4\",\"f=6\"])\n", + "plt.show()" + ] } ], "metadata": { @@ -135,4 +3737,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From b8f5ba8a80620f4d58f60a61b3f53955df0de608 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 11 Jan 2020 09:56:12 -0800 Subject: [PATCH 134/228] Give application name to cleo Application constructor --- voicechat_modem_dsp/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/__main__.py b/voicechat_modem_dsp/__main__.py index 420495f..bc8b542 100644 --- a/voicechat_modem_dsp/__main__.py +++ b/voicechat_modem_dsp/__main__.py @@ -2,7 +2,7 @@ from .cli.command_objects import TxFile -app=Application() +app=Application(name="voicechat_modem_dsp") app.add(TxFile()) From 5dc9c3e7beae88ca88bd4f0f74f4963f653c41f6 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 11 Jan 2020 09:56:42 -0800 Subject: [PATCH 135/228] Use cleaner error handling by raising CLIError and catching in decorator --- voicechat_modem_dsp/cli/command_objects.py | 35 ++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py index 66bae04..a598b55 100644 --- a/voicechat_modem_dsp/cli/command_objects.py +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -1,7 +1,21 @@ import cleo +import functools import os +class CLIError(Exception): + pass + +def exit_on_error(func): + def catch_error_and_exit(self,*args,**kwargs): + try: + return func(self,*args,**kwargs) + except CLIError as e: + self.line_error(*e.args) + return 1 + functools.update_wrapper(catch_error_and_exit,func) + return catch_error_and_exit + class TxFile(cleo.Command): """ Modulates a given datafile and saves modulated audio to an audio file @@ -13,29 +27,32 @@ class TxFile(cleo.Command): {--no-preamble : Do not include audio preamble with modulation information} {--no-toneburst : Do not include calibration toneburst} + {--raw : Shortcut for --no-preamble --no-toneburst} """ + @exit_on_error def handle(self): config_file_name=self.option("config") output_file_name=self.option("output") - self.line("Test base command") - if not config_file_name or not os.path.isfile(config_file_name): - self.line_error("A valid configuration file must be specified.", - "error") - return 1 + + # Check validity of command line options + if not config_file_name: + raise CLIError("A configuration file must be specified.","error") + if not os.path.isfile(config_file_name): + raise CLIError("Config file specified does not exist.","error") if os.path.exists(output_file_name): if os.path.isdir(output_file_name): - self.line_error("Output file {} must be writable as a file." + raise CLIError("Output file {} must be writable as a file." .format(output_file_name),"error") - return 1 if self._io.is_interactive(): result=self.confirm("Output file {} already exists. Overwrite?" .format(output_file_name)) self.line(str(result)) + if not result: + return 0 else: - self.line_error("Output file {} already exists " + raise CLIError("Output file {} already exists " "and program is in noninteractive mode." .format(output_file_name),"error") - return 1 From 1a995821901b8be8996d8d60d8d588eb1b99ccb9 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 11 Jan 2020 20:20:52 -0800 Subject: [PATCH 136/228] Baud in config file YAML is Float, not Int --- voicechat_modem_dsp/cli/config_loader.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index 3cbecec..4d9af68 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -10,25 +10,25 @@ }) ask_schema=Map({ - "baud": Int(), + "baud": Float(), "mode": Enum(["ask"]), "carrier": Float(), "amplitudes": UniqueSeq(Float()) | CommaSeparated(Float()) }) psk_schema=Map({ - "baud": Int(), + "baud": Float(), "mode": Enum(["psk"]), "carrier": Float(), "phases": UniqueSeq(Float()) | CommaSeparated(Float()) }) qam_schema=Map({ - "baud": Int(), + "baud": Float(), "mode": Enum(["qam"]), "carrier": Float(), "phases": UniqueSeq(Complex()) | CommaSeparated(Complex()) }) fsk_schema=Map({ - "baud": Int(), + "baud": Float(), "mode": Enum(["fsk"]), "amplitude": Float(), "frequencies": UniqueSeq(Float()) | CommaSeparated(Float()) From 4ea4f37c8860ffade051ffa07b0684a7fa099620 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 11 Jan 2020 21:26:21 -0800 Subject: [PATCH 137/228] Edit modulators __init__ to make importing modulator classes cleaner --- test/modulators/test_modulator_integrity.py | 5 ++--- test/modulators/test_modulator_parameters.py | 5 ++--- voicechat_modem_dsp/modulators/__init__.py | 5 +++++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/test/modulators/test_modulator_integrity.py b/test/modulators/test_modulator_integrity.py index db499f7..b445969 100644 --- a/test/modulators/test_modulator_integrity.py +++ b/test/modulators/test_modulator_integrity.py @@ -3,9 +3,8 @@ import numpy as np from voicechat_modem_dsp.encoders.encode_pad import * -from voicechat_modem_dsp.modulators.modulator_ask import ASKModulator -from voicechat_modem_dsp.modulators.modulator_fsk import FSKModulator -from voicechat_modem_dsp.modulators.modulator_utils import ModulationIntegrityWarning +from voicechat_modem_dsp.modulators import ASKModulator, FSKModulator, \ + ModulationIntegrityWarning import pytest diff --git a/test/modulators/test_modulator_parameters.py b/test/modulators/test_modulator_parameters.py index 766f72e..389aeb5 100644 --- a/test/modulators/test_modulator_parameters.py +++ b/test/modulators/test_modulator_parameters.py @@ -2,9 +2,8 @@ import numpy as np from voicechat_modem_dsp.encoders.encode_pad import * -from voicechat_modem_dsp.modulators.modulator_ask import ASKModulator -from voicechat_modem_dsp.modulators.modulator_fsk import FSKModulator -from voicechat_modem_dsp.modulators.modulator_utils import ModulationIntegrityWarning +from voicechat_modem_dsp.modulators import ASKModulator, FSKModulator, \ + ModulationIntegrityWarning import pytest diff --git a/voicechat_modem_dsp/modulators/__init__.py b/voicechat_modem_dsp/modulators/__init__.py index e69de29..e0c401b 100644 --- a/voicechat_modem_dsp/modulators/__init__.py +++ b/voicechat_modem_dsp/modulators/__init__.py @@ -0,0 +1,5 @@ +from .modulator_ask import ASKModulator +from .modulator_fsk import FSKModulator +from .modulator_utils import ModulationIntegrityWarning + +__all__=[ASKModulator, FSKModulator, ModulationIntegrityWarning] \ No newline at end of file From d6ee801b28344fe49d8927f62c2fef941cc5914e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 11 Jan 2020 21:58:54 -0800 Subject: [PATCH 138/228] Add ModulationIntegrityWarning to modulation utils typestub --- voicechat_modem_dsp/modulators/modulator_utils.pyi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.pyi b/voicechat_modem_dsp/modulators/modulator_utils.pyi index c3c7476..88197ee 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.pyi +++ b/voicechat_modem_dsp/modulators/modulator_utils.pyi @@ -4,6 +4,8 @@ from typing import Any, Sequence, Tuple from numpy import ndarray +class ModulationIntegrityWarning(UserWarning): ... + def generate_timearray(fs: float, sample_count: int) -> ndarray: ... def samples_per_symbol(fs: float, baud: float) -> float: ... def previous_resample_interpolate(timeseq: ndarray, baud: float, sequence: Sequence[float]) -> ndarray: ... From 7da9855bae3fbdb36ab8268db4a3e769fc633fac Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 11 Jan 2020 22:00:07 -0800 Subject: [PATCH 139/228] Correct types for __all__ in modulators --- voicechat_modem_dsp/modulators/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/__init__.py b/voicechat_modem_dsp/modulators/__init__.py index e0c401b..3084543 100644 --- a/voicechat_modem_dsp/modulators/__init__.py +++ b/voicechat_modem_dsp/modulators/__init__.py @@ -2,4 +2,6 @@ from .modulator_fsk import FSKModulator from .modulator_utils import ModulationIntegrityWarning -__all__=[ASKModulator, FSKModulator, ModulationIntegrityWarning] \ No newline at end of file +_public_interface=[ASKModulator, FSKModulator, ModulationIntegrityWarning] + +__all__=[type_obj.__name__ for type_obj in _public_interface] \ No newline at end of file From 4a1783698a253dd00a8b00288dedbf8c53424a8e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 11 Jan 2020 22:37:33 -0800 Subject: [PATCH 140/228] Correct name of qam_schema list entry in schema --- voicechat_modem_dsp/cli/config_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index 4d9af68..b923e30 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -25,7 +25,7 @@ "baud": Float(), "mode": Enum(["qam"]), "carrier": Float(), - "phases": UniqueSeq(Complex()) | CommaSeparated(Complex()) + "constellation": UniqueSeq(Complex()) | CommaSeparated(Complex()) }) fsk_schema=Map({ "baud": Float(), From 24e80c5f960a5eaba0f32923618092e9fbdf2285 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 11 Jan 2020 22:44:38 -0800 Subject: [PATCH 141/228] Add modulator construction helper to config loader --- voicechat_modem_dsp/cli/config_loader.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index b923e30..c020b39 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -1,6 +1,7 @@ from strictyaml import * from .yaml_schema_validators import Complex +from ..modulators import ASKModulator, FSKModulator init_config_schema=Map({ "version": Str(), @@ -57,3 +58,25 @@ def parse_config_str(string): raise YAMLValidationError("Invalid modulator found " "in modulator list",None,modulator) return config_obj + +def construct_modulators(config_dict): + fs=config_dict["fs"] + modulator_list=list() + for modulator_config in config_dict["modulators"]: + baud=modulator_config["baud"] + mode=modulator_config["mode"] + if mode=="ask": + carrier=modulator_config["carrier"] + amplitudes=modulator_config["amplitudes"] + modulator_list.append(ASKModulator(fs, carrier, amplitudes, baud)) + elif mode=="psk": + pass + elif mode=="qam": + pass + elif mode=="fsk": + amplitude=modulator_config["amplitude"] + frequencies=modulator_config["frequencies"] + modulator_list.append( + FSKModulator(fs, amplitude, frequencies, baud)) + else: + raise ValueError("Mapping has invalid mode key") From f28dd1a8d2dd2dce6863a347c62345a229a7a7d3 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 11 Jan 2020 23:13:20 -0800 Subject: [PATCH 142/228] Move datastream dicts into __init__ and expose from __all__ --- voicechat_modem_dsp/encoders/__init__.py | 15 +++++++++++++++ voicechat_modem_dsp/encoders/encode_pad.py | 13 ------------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/voicechat_modem_dsp/encoders/__init__.py b/voicechat_modem_dsp/encoders/__init__.py index e69de29..e148cb9 100644 --- a/voicechat_modem_dsp/encoders/__init__.py +++ b/voicechat_modem_dsp/encoders/__init__.py @@ -0,0 +1,15 @@ +from .encode_pad import * + +# Convenience mapping to allow for lookup based on len(modulation_list) +encode_function_mappings: Dict[int,Callable[[readable_bytearr], List[int]]]= \ + {2:base_2_encode, 4:base_4_encode, + 8:base_8_encode, 16:base_16_encode, + 32:base_32_encode, 64:base_64_encode, + 256:base_256_encode} +decode_function_mappings: Dict[int,Callable[[List[int]], readable_bytearr]]= \ + {2:base_2_decode, 4:base_4_decode, + 8:base_8_decode, 16:base_16_decode, + 32:base_32_decode, 64:base_64_decode, + 256:base_256_decode} + +__all__=["encode_function_mappings","decode_function_mappings"] diff --git a/voicechat_modem_dsp/encoders/encode_pad.py b/voicechat_modem_dsp/encoders/encode_pad.py index b3a79c8..ab369ab 100644 --- a/voicechat_modem_dsp/encoders/encode_pad.py +++ b/voicechat_modem_dsp/encoders/encode_pad.py @@ -178,16 +178,3 @@ def base_256_decode(datastream: List[int]) -> readable_bytearr: return bytes(datastream) except ValueError: raise ValueError("Illegal symbol detected in datastream") - -# Convenience mapping to allow for lookup based on len(modulation_list) -#encode_function_mappings: Dict[int,Callable[[readable_bytearr], List[int]]] -#decode_function_mappings: Dict[int,Callable[[List[int]], readable_bytearr]] -encode_function_mappings = {2:base_2_encode, 4:base_4_encode, - 8:base_8_encode, 16:base_16_encode, - 32:base_32_encode, 64:base_64_encode, - 256:base_256_encode} - -decode_function_mappings = {2:base_2_decode, 4:base_4_decode, - 8:base_8_decode, 16:base_16_decode, - 32:base_32_decode, 64:base_64_decode, - 256:base_256_decode} From a29a2375f286eb3737e91d87659df6f39cfa9481 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 12 Jan 2020 00:18:01 -0800 Subject: [PATCH 143/228] Forgot to return modulator list from constructor helper --- voicechat_modem_dsp/cli/config_loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index c020b39..2a4dc91 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -80,3 +80,4 @@ def construct_modulators(config_dict): FSKModulator(fs, amplitude, frequencies, baud)) else: raise ValueError("Mapping has invalid mode key") + return modulator_list From ff807f808ca27d174ea7f564d00eac58b2775072 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 12 Jan 2020 00:18:10 -0800 Subject: [PATCH 144/228] Initial working TxFile command --- voicechat_modem_dsp/cli/command_objects.py | 76 ++++++++++++++++++++-- voicechat_modem_dsp/cli/config_loader.py | 2 +- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py index a598b55..d70de49 100644 --- a/voicechat_modem_dsp/cli/command_objects.py +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -2,10 +2,24 @@ import functools import os +from scipy.io import wavfile +from strictyaml import YAMLValidationError + +from .config_loader import parse_config_str, construct_modulators +from ..modulators import ASKModulator, FSKModulator +from ..encoders import encode_function_mappings, decode_function_mappings + +""" +Error raised to exit from cleo.Command.handle early +""" class CLIError(Exception): pass +""" +Decorator that catches CLIErrors, prints the error, +and exits with a nonzero status code +""" def exit_on_error(func): def catch_error_and_exit(self,*args,**kwargs): try: @@ -21,12 +35,12 @@ class TxFile(cleo.Command): Modulates a given datafile and saves modulated audio to an audio file transmit_file - {filename : Data file to modulate} + {input-file : Data file to modulate} {--o|output=modulated.wav : Output file for audio} {--config= : Modulation configuration file} - {--no-preamble : Do not include audio preamble with + {--no-header : Do not include audio header with modulation information} - {--no-toneburst : Do not include calibration toneburst} + {--no-preamble : Do not include calibration preamble} {--raw : Shortcut for --no-preamble --no-toneburst} """ @@ -34,6 +48,7 @@ class TxFile(cleo.Command): def handle(self): config_file_name=self.option("config") output_file_name=self.option("output") + input_file_name=self.argument("input-file") # Check validity of command line options if not config_file_name: @@ -49,10 +64,63 @@ def handle(self): if self._io.is_interactive(): result=self.confirm("Output file {} already exists. Overwrite?" .format(output_file_name)) - self.line(str(result)) if not result: return 0 else: raise CLIError("Output file {} already exists " "and program is in noninteractive mode." .format(output_file_name),"error") + + if not os.path.isfile(input_file_name): + raise CLIError("Input file {} must exist." + .format(input_file_name),"error") + + has_header = not (self.option("no-header") or self.option("raw")) + has_preamble = not (self.option("no-preamble") or self.option("raw")) + + # TODO: obviously temporary; fix once prerequisites are done + if has_header or has_preamble: + raise CLIError("Headers and preambles are not yet supported.") + + self.line("Reading config file...") + try: + with open(config_file_name, "r") as fil: + config_text=fil.read() + config_obj=parse_config_str(config_text) + except YAMLValidationError as e: + # e.args[0] is the error message + raise CLIError(e.args[0],"error") + + # TODO: obviously temporary; fix once prerequisites are done + if len(config_obj["modulators"])>1: + raise CLIError("Multiplexing modulators is not yet supported.") + + modulator_objects=construct_modulators(config_obj.data) + # TODO: construct OFDM once that is complete + modulator_obj=modulator_objects[0] + + if modulator_obj.fs!=int(modulator_obj.fs): + raise CLIError("Sampling rate must be an integer (for now).") + + if isinstance(modulator_obj,ASKModulator): + constellation_length=len(modulator_obj.amp_list) + elif isinstance(modulator_obj,FSKModulator): + constellation_length=len(modulator_obj.freq_list) + else: + raise CLIError("Modulator type is not yet supported.") + + self.line("Encoding data...") + try: + datastream_encoder=encode_function_mappings[constellation_length] + except KeyError: + raise CLIError("Unsupported count of constellation values.") + + with open(input_file_name,"rb") as fil: + bitstream=fil.read() + datastream=datastream_encoder(bitstream) + self.line("Modulating data...") + modulated_datastream=modulator_obj.modulate(datastream) + + self.line("Writing audio file...") + wavfile.write(output_file_name, int(modulator_obj.fs), + modulated_datastream) \ No newline at end of file diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index 2a4dc91..f20aea4 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -37,7 +37,7 @@ # TODO: more informative ValidationError messages # This is partially due to underdocumented libraries -def parse_config_str(string): +def parse_config_str(string: str) -> YAML: config_obj=load(string,init_config_schema) # TODO: check version number if config_obj["fs"].data<=0: From bc23ee8a1b830517980ca2188a14e4a9cdb8580b Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 13 Jan 2020 18:38:17 -0800 Subject: [PATCH 145/228] Refactoring of CLI utilities and continued work on TxFile --- voicechat_modem_dsp/cli/command_objects.py | 39 +++++++------------- voicechat_modem_dsp/cli/command_utils.py | 41 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 27 deletions(-) create mode 100644 voicechat_modem_dsp/cli/command_utils.py diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py index d70de49..25b9482 100644 --- a/voicechat_modem_dsp/cli/command_objects.py +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -7,17 +7,13 @@ from strictyaml import YAMLValidationError from .config_loader import parse_config_str, construct_modulators +from .command_utils import CLIError, ExtendedCommand + from ..modulators import ASKModulator, FSKModulator from ..encoders import encode_function_mappings, decode_function_mappings """ -Error raised to exit from cleo.Command.handle early -""" -class CLIError(Exception): - pass - -""" -Decorator that catches CLIErrors, prints the error, +Decorator for class methods that catches CLIErrors, prints the error, and exits with a nonzero status code """ def exit_on_error(func): @@ -30,7 +26,7 @@ def catch_error_and_exit(self,*args,**kwargs): functools.update_wrapper(catch_error_and_exit,func) return catch_error_and_exit -class TxFile(cleo.Command): +class TxFile(ExtendedCommand): """ Modulates a given datafile and saves modulated audio to an audio file @@ -41,7 +37,7 @@ class TxFile(cleo.Command): {--no-header : Do not include audio header with modulation information} {--no-preamble : Do not include calibration preamble} - {--raw : Shortcut for --no-preamble --no-toneburst} + {--raw : Shortcut for --no-header --no-preamble} """ @exit_on_error @@ -54,23 +50,11 @@ def handle(self): if not config_file_name: raise CLIError("A configuration file must be specified.","error") if not os.path.isfile(config_file_name): - raise CLIError("Config file specified does not exist.","error") - - if os.path.exists(output_file_name): - if os.path.isdir(output_file_name): - raise CLIError("Output file {} must be writable as a file." - .format(output_file_name),"error") - - if self._io.is_interactive(): - result=self.confirm("Output file {} already exists. Overwrite?" - .format(output_file_name)) - if not result: - return 0 - else: - raise CLIError("Output file {} already exists " - "and program is in noninteractive mode." - .format(output_file_name),"error") - + raise CLIError("Configuration file {} does not exist." + .format(config_file_name),"error") + + self.confirm_file_writable(output_file_name) + if not os.path.isfile(input_file_name): raise CLIError("Input file {} must exist." .format(input_file_name),"error") @@ -82,6 +66,7 @@ def handle(self): if has_header or has_preamble: raise CLIError("Headers and preambles are not yet supported.") + # Read in config file and check its validity self.line("Reading config file...") try: with open(config_file_name, "r") as fil: @@ -123,4 +108,4 @@ def handle(self): self.line("Writing audio file...") wavfile.write(output_file_name, int(modulator_obj.fs), - modulated_datastream) \ No newline at end of file + modulated_datastream) diff --git a/voicechat_modem_dsp/cli/command_utils.py b/voicechat_modem_dsp/cli/command_utils.py new file mode 100644 index 0000000..a9ecc4d --- /dev/null +++ b/voicechat_modem_dsp/cli/command_utils.py @@ -0,0 +1,41 @@ +import cleo + +import os.path + +class CLIError(Exception): + """ + Error raised to exit from cleo.Command.handle early + """ + pass + +class FileExistsAndCannotOverwriteException(CLIError): + """ + Error indicating file exists and cannot overwrite + + Subclass of CLIError + """ + pass + +class ExtendedCommand(cleo.Command): + """cleo.Command extended with additional utility functions""" + + """Confirm whether a file is writable and raise an exception if not""" + def confirm_file_writable(self, filename: str) -> None: + if os.path.exists(filename): + if os.path.isdir(filename): + raise FileExistsAndCannotOverwriteException( + "Output file {} must be writable as a file." + .format(filename),"error") + + if self.io.is_interactive(): + result=self.confirm("Output file {} already exists. Overwrite?" + .format(filename)) + if not result: + raise FileExistsAndCannotOverwriteException( + "Choosing not to overwrite existing output file {}." + .format(filename)) + else: + raise FileExistsAndCannotOverwriteException( + "Output file {} already exists " + "and program is in noninteractive mode." + .format(filename),"error") From 68e45c5403bb25f176c1eda632d92243a376a8fa Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 13 Jan 2020 20:01:05 -0800 Subject: [PATCH 146/228] Move docstrings in modulator_utils.py to proper place --- .../modulators/modulator_utils.py | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 5d8a1de..ab6b326 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -2,49 +2,48 @@ from scipy import signal from scipy.interpolate import interp1d -""" -Warning to be raised when modulators cannot guarantee data accuracy -""" class ModulationIntegrityWarning(UserWarning): + """ + Warning to be raised when modulators cannot guarantee data accuracy + """ pass -""" -Computes a time array given a sampling rate and a sample count -""" def generate_timearray(fs, sample_count): + """ + Computes a time array given a sampling rate and a sample count + """ dt=1/fs return np.linspace(0,dt*sample_count,sample_count,endpoint=False) -""" -Computes the number of samples per symbol given a baud and sampling rate - -Note: Neither quantity has to be an integer -""" def samples_per_symbol(fs, baud): + """ + Computes the number of samples per symbol given a baud and sampling rate + + Note: Neither quantity has to be an integer + """ return fs/baud -""" -Maps a sequence (assumed to be sampled at baud rate) to a timesequence -""" def previous_resample_interpolate(timeseq, baud, data): + """ + Maps a sequence (assumed to be sampled at baud rate) to a timesequence + """ interp_func=interp1d(range(len(data)),data, kind="previous",copy=False,bounds_error=False,fill_value=0.0) return interp_func(timeseq*baud) -""" -Computes the average of the data over the specified interval with integrals. - -The bounds on the given interval need not be integers. -Linear interpolation is used for noninteger bounds. - -Note: Due to trapezoidal approximation this will not produce the -normal average if the bounds are integers. -Both endpoints are explicitly included, unlike normal array slicing. -In addition, the values at the extremities receive half the weight -as the rest of the data, following the trapezoidal integration formula. -""" - def average_interval_data(data, begin, end): + """ + Computes the average of the data over the specified interval with integrals. + + The bounds on the given interval need not be integers. + Linear interpolation is used for noninteger bounds. + + Note: Due to trapezoidal approximation this will not produce the + normal average if the bounds are integers. + Both endpoints are explicitly included, unlike normal array slicing. + In addition, the values at the extremities receive half the weight + as the rest of the data, following the trapezoidal integration formula. + """ if end(len(data)-1): @@ -82,10 +81,10 @@ def average_interval_data(data, begin, end): return np.trapz(y_array,x_array)/width -""" -Computes a gaussian smoothing filter given sampling rate and sigma time -""" def gaussian_window(fs, sigma_dt): + """ + Computes a gaussian smoothing filter given sampling rate and sigma time + """ sigma=sigma_dt*fs sample_count=np.ceil(6*sigma+1) if sample_count%2==0: @@ -94,13 +93,13 @@ def gaussian_window(fs, sigma_dt): raw_window_sum=np.sum(raw_window) return raw_window/raw_window_sum - -""" -Uses fred harris' rule-of-thumb to estimate a FIR tap count - -Formula from https://dsp.stackexchange.com/questions/37646/filter-order-rule-of-thumb -""" def fred_harris_fir_tap_count(fs, transition_width, db_attenuation): + """ + Uses fred harris' rule-of-thumb to estimate a FIR tap count + + Formula from + https://dsp.stackexchange.com/questions/37646/filter-order-rule-of-thumb + """ #N=[fs/delta(f)]∗[atten(dB)/22] filter_tap_count=fs/transition_width filter_tap_count*=db_attenuation/22 @@ -109,11 +108,11 @@ def fred_harris_fir_tap_count(fs, transition_width, db_attenuation): filter_tap_count+=1 return filter_tap_count -""" -Computes lowpass FIR filter given cutoffs -Uses scipy.signal.firls for Least Squares FIR Filter Design -""" def lowpass_fir_filter(fs,cutoff_low,cutoff_high,attenuation=80): + """ + Computes lowpass FIR filter given cutoffs + Uses scipy.signal.firls for Least Squares FIR Filter Design + """ if cutoff_low>=cutoff_high: raise ValueError("High cutoff must be larger than low cutoff") if cutoff_low>=0.5*fs or cutoff_high>=0.5*fs: @@ -130,23 +129,24 @@ def lowpass_fir_filter(fs,cutoff_low,cutoff_high,attenuation=80): # TODO: remove zeros? return lowpass_filt -""" -Helper function that takes symmetric "linear-phase" FIR filter -and makes it truly linear-phase by removing zeros - -Based on the technique described in https://www.cypress.com/file/123191/download -""" def linearize_fir(fir_filter): + """ + Helper function that takes symmetric "linear-phase" FIR filter + and makes it truly linear-phase by removing zeros + + Based on the technique described in + https://www.cypress.com/file/123191/download + """ frequencies,response=signal.freqz(fir_filter) # TODO fill in the rest of this code if this ends up being actually used raise NotImplementedError -""" -Helper function to computer the IIR filter for the Goertzel algorithm - -Derivation of formula from https://www.dsprelated.com/showarticle/796.php -""" def goertzel_iir(freq,fs): + """ + Helper function to computer the IIR filter for the Goertzel algorithm + + Derivation of formula from https://www.dsprelated.com/showarticle/796.php + """ if freq>=0.5*fs: raise ValueError("Desired peak frequency is too high") norm_freq=2*np.pi*freq/fs From 2dc4bee67cdd83b79303bbc878a1b047ba30b7b3 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 13 Jan 2020 20:01:46 -0800 Subject: [PATCH 147/228] Update comment in lowpass_fir_filter about using firls --- voicechat_modem_dsp/modulators/modulator_utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index ab6b326..5c69ff2 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -121,9 +121,7 @@ def lowpass_fir_filter(fs,cutoff_low,cutoff_high,attenuation=80): tap_count=fred_harris_fir_tap_count(fs,cutoff_high-cutoff_low,attenuation) # Remez would sometimes return NaN arrays as filters - # Least-Squares is not iterative so it may have better stability? - #lowpass_filt=signal.remez(tap_count, - # [0,cutoff_low,cutoff_high,0.5*fs],[1,0],fs=fs) + # Least-Squares is not iterative so it may have better stability lowpass_filt=signal.firls(tap_count,[0,cutoff_low,cutoff_high,0.5*fs], [1,1,0,0],fs=fs) # TODO: remove zeros? From 2845d95386212013d99f2acea5d960ed29d570a0 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 13 Jan 2020 20:01:05 -0800 Subject: [PATCH 148/228] Move docstrings in modulator_utils.py to proper place (cherry picked from commit 68e45c5403bb25f176c1eda632d92243a376a8fa) --- .../modulators/modulator_utils.py | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 5d8a1de..ab6b326 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -2,49 +2,48 @@ from scipy import signal from scipy.interpolate import interp1d -""" -Warning to be raised when modulators cannot guarantee data accuracy -""" class ModulationIntegrityWarning(UserWarning): + """ + Warning to be raised when modulators cannot guarantee data accuracy + """ pass -""" -Computes a time array given a sampling rate and a sample count -""" def generate_timearray(fs, sample_count): + """ + Computes a time array given a sampling rate and a sample count + """ dt=1/fs return np.linspace(0,dt*sample_count,sample_count,endpoint=False) -""" -Computes the number of samples per symbol given a baud and sampling rate - -Note: Neither quantity has to be an integer -""" def samples_per_symbol(fs, baud): + """ + Computes the number of samples per symbol given a baud and sampling rate + + Note: Neither quantity has to be an integer + """ return fs/baud -""" -Maps a sequence (assumed to be sampled at baud rate) to a timesequence -""" def previous_resample_interpolate(timeseq, baud, data): + """ + Maps a sequence (assumed to be sampled at baud rate) to a timesequence + """ interp_func=interp1d(range(len(data)),data, kind="previous",copy=False,bounds_error=False,fill_value=0.0) return interp_func(timeseq*baud) -""" -Computes the average of the data over the specified interval with integrals. - -The bounds on the given interval need not be integers. -Linear interpolation is used for noninteger bounds. - -Note: Due to trapezoidal approximation this will not produce the -normal average if the bounds are integers. -Both endpoints are explicitly included, unlike normal array slicing. -In addition, the values at the extremities receive half the weight -as the rest of the data, following the trapezoidal integration formula. -""" - def average_interval_data(data, begin, end): + """ + Computes the average of the data over the specified interval with integrals. + + The bounds on the given interval need not be integers. + Linear interpolation is used for noninteger bounds. + + Note: Due to trapezoidal approximation this will not produce the + normal average if the bounds are integers. + Both endpoints are explicitly included, unlike normal array slicing. + In addition, the values at the extremities receive half the weight + as the rest of the data, following the trapezoidal integration formula. + """ if end(len(data)-1): @@ -82,10 +81,10 @@ def average_interval_data(data, begin, end): return np.trapz(y_array,x_array)/width -""" -Computes a gaussian smoothing filter given sampling rate and sigma time -""" def gaussian_window(fs, sigma_dt): + """ + Computes a gaussian smoothing filter given sampling rate and sigma time + """ sigma=sigma_dt*fs sample_count=np.ceil(6*sigma+1) if sample_count%2==0: @@ -94,13 +93,13 @@ def gaussian_window(fs, sigma_dt): raw_window_sum=np.sum(raw_window) return raw_window/raw_window_sum - -""" -Uses fred harris' rule-of-thumb to estimate a FIR tap count - -Formula from https://dsp.stackexchange.com/questions/37646/filter-order-rule-of-thumb -""" def fred_harris_fir_tap_count(fs, transition_width, db_attenuation): + """ + Uses fred harris' rule-of-thumb to estimate a FIR tap count + + Formula from + https://dsp.stackexchange.com/questions/37646/filter-order-rule-of-thumb + """ #N=[fs/delta(f)]∗[atten(dB)/22] filter_tap_count=fs/transition_width filter_tap_count*=db_attenuation/22 @@ -109,11 +108,11 @@ def fred_harris_fir_tap_count(fs, transition_width, db_attenuation): filter_tap_count+=1 return filter_tap_count -""" -Computes lowpass FIR filter given cutoffs -Uses scipy.signal.firls for Least Squares FIR Filter Design -""" def lowpass_fir_filter(fs,cutoff_low,cutoff_high,attenuation=80): + """ + Computes lowpass FIR filter given cutoffs + Uses scipy.signal.firls for Least Squares FIR Filter Design + """ if cutoff_low>=cutoff_high: raise ValueError("High cutoff must be larger than low cutoff") if cutoff_low>=0.5*fs or cutoff_high>=0.5*fs: @@ -130,23 +129,24 @@ def lowpass_fir_filter(fs,cutoff_low,cutoff_high,attenuation=80): # TODO: remove zeros? return lowpass_filt -""" -Helper function that takes symmetric "linear-phase" FIR filter -and makes it truly linear-phase by removing zeros - -Based on the technique described in https://www.cypress.com/file/123191/download -""" def linearize_fir(fir_filter): + """ + Helper function that takes symmetric "linear-phase" FIR filter + and makes it truly linear-phase by removing zeros + + Based on the technique described in + https://www.cypress.com/file/123191/download + """ frequencies,response=signal.freqz(fir_filter) # TODO fill in the rest of this code if this ends up being actually used raise NotImplementedError -""" -Helper function to computer the IIR filter for the Goertzel algorithm - -Derivation of formula from https://www.dsprelated.com/showarticle/796.php -""" def goertzel_iir(freq,fs): + """ + Helper function to computer the IIR filter for the Goertzel algorithm + + Derivation of formula from https://www.dsprelated.com/showarticle/796.php + """ if freq>=0.5*fs: raise ValueError("Desired peak frequency is too high") norm_freq=2*np.pi*freq/fs From 7702beb0074526b720b3ff8f06943deca1499ed2 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 13 Jan 2020 20:01:46 -0800 Subject: [PATCH 149/228] Update comment in lowpass_fir_filter about using firls (cherry picked from commit 2dc4bee67cdd83b79303bbc878a1b047ba30b7b3) --- voicechat_modem_dsp/modulators/modulator_utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index ab6b326..5c69ff2 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -121,9 +121,7 @@ def lowpass_fir_filter(fs,cutoff_low,cutoff_high,attenuation=80): tap_count=fred_harris_fir_tap_count(fs,cutoff_high-cutoff_low,attenuation) # Remez would sometimes return NaN arrays as filters - # Least-Squares is not iterative so it may have better stability? - #lowpass_filt=signal.remez(tap_count, - # [0,cutoff_low,cutoff_high,0.5*fs],[1,0],fs=fs) + # Least-Squares is not iterative so it may have better stability lowpass_filt=signal.firls(tap_count,[0,cutoff_low,cutoff_high,0.5*fs], [1,1,0,0],fs=fs) # TODO: remove zeros? From 7c24530ad5ac5658c503b0cde19549305143af09 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 13 Jan 2020 20:10:23 -0800 Subject: [PATCH 150/228] Include the cli package in type checking --- mypy.ini | 5 +++++ run_mypy.sh | 8 +++++--- voicechat_modem_dsp/cli/config_loader.py | 4 +++- voicechat_modem_dsp/stubs/scipy/io/__init__.pyi | 1 + voicechat_modem_dsp/stubs/scipy/io/wavfile.pyi | 7 +++++++ 5 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 mypy.ini create mode 100644 voicechat_modem_dsp/stubs/scipy/io/__init__.pyi create mode 100644 voicechat_modem_dsp/stubs/scipy/io/wavfile.pyi diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..3ec3e8c --- /dev/null +++ b/mypy.ini @@ -0,0 +1,5 @@ +[mypy-strictyaml] +ignore_missing_imports=True + +[mypy-cleo] +ignore_missing_imports=True diff --git a/run_mypy.sh b/run_mypy.sh index 3e39029..8dea5bd 100644 --- a/run_mypy.sh +++ b/run_mypy.sh @@ -1,6 +1,8 @@ #!/bin/sh -cd voicechat_modem_dsp || exit 1 -mypy -p encoders -p modulators +#cd voicechat_modem_dsp || exit 1 + +MYPYPATH=voicechat_modem_dsp/stubs mypy -p voicechat_modem_dsp test_status=$? -cd .. || exit 1 + +#cd .. || exit 1 exit $test_status diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index f20aea4..d6f0989 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -1,4 +1,6 @@ -from strictyaml import * +# Import the schema types first, and then the actual loading stuff +from strictyaml import Map, Str, Float, Enum, Any, UniqueSeq, CommaSeparated +from strictyaml import load, YAML, YAMLValidationError from .yaml_schema_validators import Complex from ..modulators import ASKModulator, FSKModulator diff --git a/voicechat_modem_dsp/stubs/scipy/io/__init__.pyi b/voicechat_modem_dsp/stubs/scipy/io/__init__.pyi new file mode 100644 index 0000000..14f90df --- /dev/null +++ b/voicechat_modem_dsp/stubs/scipy/io/__init__.pyi @@ -0,0 +1 @@ +from . import wavfile \ No newline at end of file diff --git a/voicechat_modem_dsp/stubs/scipy/io/wavfile.pyi b/voicechat_modem_dsp/stubs/scipy/io/wavfile.pyi new file mode 100644 index 0000000..f1a8501 --- /dev/null +++ b/voicechat_modem_dsp/stubs/scipy/io/wavfile.pyi @@ -0,0 +1,7 @@ +from typing import Any, IO, Union, Tuple + +from numpy import ndarray + +def read(filename: Union[str, IO[Any]], mmap: bool=...) \ + -> Tuple[int, ndarray]: ... +def write(filename: Union[str, IO[Any]], rate: int, data: ndarray) -> None: ... \ No newline at end of file From 884c9cb7693500d47b8a1383eac2d2d66d4772a9 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 13 Jan 2020 20:20:06 -0800 Subject: [PATCH 151/228] Add missing typing imports to encoders __init__ --- voicechat_modem_dsp/encoders/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/voicechat_modem_dsp/encoders/__init__.py b/voicechat_modem_dsp/encoders/__init__.py index e148cb9..3e7a264 100644 --- a/voicechat_modem_dsp/encoders/__init__.py +++ b/voicechat_modem_dsp/encoders/__init__.py @@ -1,5 +1,8 @@ from .encode_pad import * +from typing import Dict, Callable, List +from .bitstream import readable_bytearr + # Convenience mapping to allow for lookup based on len(modulation_list) encode_function_mappings: Dict[int,Callable[[readable_bytearr], List[int]]]= \ {2:base_2_encode, 4:base_4_encode, From 8ac4c8f1bf93635b77e9badb68d20c45170a4a19 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 13 Jan 2020 20:41:11 -0800 Subject: [PATCH 152/228] Further type annotation stuff Hope to check interiors of functions after dealing with other things --- mypy.ini | 3 +++ voicechat_modem_dsp/cli/config_loader.py | 9 ++++++--- voicechat_modem_dsp/modulators/__init__.py | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mypy.ini b/mypy.ini index 3ec3e8c..ad22d82 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,3 +1,6 @@ +[mypy] +#check_untyped_defs=True + [mypy-strictyaml] ignore_missing_imports=True diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index d6f0989..aed65bf 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -3,7 +3,10 @@ from strictyaml import load, YAML, YAMLValidationError from .yaml_schema_validators import Complex -from ..modulators import ASKModulator, FSKModulator +from ..modulators import ASKModulator, FSKModulator, Modulator + +from typing import List as TypingList +from typing import Mapping as TypingMap init_config_schema=Map({ "version": Str(), @@ -61,9 +64,9 @@ def parse_config_str(string: str) -> YAML: "in modulator list",None,modulator) return config_obj -def construct_modulators(config_dict): +def construct_modulators(config_dict: TypingMap) -> TypingList[Modulator]: fs=config_dict["fs"] - modulator_list=list() + modulator_list: TypingList[Modulator]=list() for modulator_config in config_dict["modulators"]: baud=modulator_config["baud"] mode=modulator_config["mode"] diff --git a/voicechat_modem_dsp/modulators/__init__.py b/voicechat_modem_dsp/modulators/__init__.py index 3084543..5203112 100644 --- a/voicechat_modem_dsp/modulators/__init__.py +++ b/voicechat_modem_dsp/modulators/__init__.py @@ -1,7 +1,9 @@ from .modulator_ask import ASKModulator from .modulator_fsk import FSKModulator +from .modulator_base import Modulator from .modulator_utils import ModulationIntegrityWarning -_public_interface=[ASKModulator, FSKModulator, ModulationIntegrityWarning] +_public_interface=[ASKModulator, FSKModulator, Modulator, + ModulationIntegrityWarning] __all__=[type_obj.__name__ for type_obj in _public_interface] \ No newline at end of file From ab8164f11f665652bf91b692237b486d71c6eb22 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 15 Jan 2020 18:50:14 -0800 Subject: [PATCH 153/228] Add __all__ attribute to ecc module --- voicechat_modem_dsp/encoders/ecc/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/voicechat_modem_dsp/encoders/ecc/__init__.py b/voicechat_modem_dsp/encoders/ecc/__init__.py index e69de29..2326038 100644 --- a/voicechat_modem_dsp/encoders/ecc/__init__.py +++ b/voicechat_modem_dsp/encoders/ecc/__init__.py @@ -0,0 +1,3 @@ +from .hamming_7_4 import hamming_encode_7_4, hamming_decode_7_4 + +__all__=["hamming_encode_7_4","hamming_decode_7_4"] \ No newline at end of file From 37e73452ba3914b071efda7097e82bc10101dc35 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 15 Jan 2020 18:51:48 -0800 Subject: [PATCH 154/228] Add check for ECC in TxFile Command --- voicechat_modem_dsp/cli/command_objects.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py index 25b9482..d70d19c 100644 --- a/voicechat_modem_dsp/cli/command_objects.py +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -11,6 +11,7 @@ from ..modulators import ASKModulator, FSKModulator from ..encoders import encode_function_mappings, decode_function_mappings +from ..encoders.ecc import hamming_7_4 """ Decorator for class methods that catches CLIErrors, prints the error, @@ -102,6 +103,13 @@ def handle(self): with open(input_file_name,"rb") as fil: bitstream=fil.read() + if config_obj["ecc"].data in ["none","raw"]: + pass + elif config_obj["ecc"].data in ["hamming_7_4"]: + bitstream=hamming_7_4.hamming_encode_7_4(bitstream) + else: + # Should never happen + raise CLIError("Invalid ECC mode found late; should have been caught earlier","error") datastream=datastream_encoder(bitstream) self.line("Modulating data...") modulated_datastream=modulator_obj.modulate(datastream) From b658bce5f58b9c7f59db3c81e9d6b33395571e03 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 15 Jan 2020 18:52:21 -0800 Subject: [PATCH 155/228] Check that output file is writable after checking for input file in TxFile --- voicechat_modem_dsp/cli/command_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py index d70d19c..a5d6d79 100644 --- a/voicechat_modem_dsp/cli/command_objects.py +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -54,12 +54,12 @@ def handle(self): raise CLIError("Configuration file {} does not exist." .format(config_file_name),"error") - self.confirm_file_writable(output_file_name) - if not os.path.isfile(input_file_name): raise CLIError("Input file {} must exist." .format(input_file_name),"error") + self.confirm_file_writable(output_file_name) + has_header = not (self.option("no-header") or self.option("raw")) has_preamble = not (self.option("no-preamble") or self.option("raw")) From 12aae2fda99456b247deab942d501a867c928790 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 15 Jan 2020 18:52:57 -0800 Subject: [PATCH 156/228] Write an RxFile command This still needs to be looked over --- voicechat_modem_dsp/__main__.py | 3 +- voicechat_modem_dsp/cli/command_objects.py | 83 ++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/__main__.py b/voicechat_modem_dsp/__main__.py index bc8b542..433a40c 100644 --- a/voicechat_modem_dsp/__main__.py +++ b/voicechat_modem_dsp/__main__.py @@ -1,10 +1,11 @@ from cleo import Application -from .cli.command_objects import TxFile +from .cli.command_objects import TxFile, RxFile app=Application(name="voicechat_modem_dsp") app.add(TxFile()) +app.add(RxFile()) if __name__=="__main__": app.run() \ No newline at end of file diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py index a5d6d79..e2f6d32 100644 --- a/voicechat_modem_dsp/cli/command_objects.py +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -117,3 +117,86 @@ def handle(self): self.line("Writing audio file...") wavfile.write(output_file_name, int(modulator_obj.fs), modulated_datastream) + +class RxFile(ExtendedCommand): + """ + Deodulates a given audiofile and saves demodulated data to a file + + receive_file + {input-file : Wave file to demodulate} + {--o|output= : Output file for audio} + {--config= : Modulation configuration file (when header is missing)} + """ + + @exit_on_error + def handle(self): + config_file_name=self.option("config") + output_file_name=self.option("output") + input_file_name=self.argument("input-file") + + # Check validity of command line options + if not output_file_name: + raise CLIError("An output file must be specified.","error") + + if not os.path.isfile(input_file_name): + raise CLIError("Input file {} must exist." + .format(input_file_name),"error") + + self.confirm_file_writable(output_file_name) + + # TODO: try to extract information from header once that is implemented + self.line("Falling back onto reading config file...") + + if not config_file_name: + raise CLIError("A configuration file must be specified.","error") + if not os.path.isfile(config_file_name): + raise CLIError("Configuration file {} does not exist." + .format(config_file_name),"error") + + try: + with open(config_file_name, "r") as fil: + config_text=fil.read() + config_obj=parse_config_str(config_text) + except YAMLValidationError as e: + # e.args[0] is the error message + raise CLIError(e.args[0],"error") + + # TODO: obviously temporary; fix once prerequisites are done + if len(config_obj["modulators"])>1: + raise CLIError("Multiplexing modulators is not yet supported.") + + modulator_objects=construct_modulators(config_obj.data) + # TODO: construct OFDM once that is complete + modulator_obj=modulator_objects[0] + if isinstance(modulator_obj,ASKModulator): + constellation_length=len(modulator_obj.amp_list) + elif isinstance(modulator_obj,FSKModulator): + constellation_length=len(modulator_obj.freq_list) + else: + raise CLIError("Modulator type is not yet supported.") + + try: + datastream_decoder=decode_function_mappings[constellation_length] + except KeyError: + raise CLIError("Unsupported count of constellation values.") + + self.line("Reading audio file...") + wavfile_fs, modulated_datastream = wavfile.read(input_file_name) + if wavfile_fs != config_obj["fs"].data: + raise CLIError("Sampling frequency mismatch in config and audio", + "error") + self.line("Demodulating audio...") + datastream=modulator_obj.demodulate(modulated_datastream) + bitstream=datastream_decoder(datastream) + + self.line("Writing demodulated data...") + if config_obj["ecc"].data in ["none","raw"]: + pass + elif config_obj["ecc"].data in ["hamming_7_4"]: + bitstream=hamming_7_4.hamming_decode_7_4(bitstream) + else: + # Should never happen + raise CLIError("Invalid ECC mode found late; should have been caught earlier","error") + + with open(output_file_name,"wb") as fil: + fil.write(bitstream) From 57f532a5f41695b9ced074d83ea8e477e2e3ff97 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 15 Jan 2020 18:53:34 -0800 Subject: [PATCH 157/228] Indicate sign characters for Cartesian constellation points in spec --- docs/specs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specs/config.rst b/docs/specs/config.rst index 2c5897e..b4b52e1 100644 --- a/docs/specs/config.rst +++ b/docs/specs/config.rst @@ -61,7 +61,7 @@ The parameters for the ``qam`` mode are as follows: - ``baud:`` The symbol rate - ``carrier:`` The carrier frequency - ``constellation:`` A list of constellation points, specified either as - ``{re}+{im}i`` or as ``({amp}, {phase})`` + ``{re}(+/-){im}i`` or as ``({amp}, {phase})`` .. note:: When specifying points in Cartesian form, the imaginary component From ac91589a1b7b448b9201c2bc77b3b5c609b6bd79 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Wed, 15 Jan 2020 19:10:29 -0800 Subject: [PATCH 158/228] Use comment type annotations for Python 3.5 support --- voicechat_modem_dsp/cli/config_loader.py | 2 +- voicechat_modem_dsp/encoders/__init__.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index aed65bf..92e927d 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -66,7 +66,7 @@ def parse_config_str(string: str) -> YAML: def construct_modulators(config_dict: TypingMap) -> TypingList[Modulator]: fs=config_dict["fs"] - modulator_list: TypingList[Modulator]=list() + modulator_list=list() # type: TypingList[Modulator] for modulator_config in config_dict["modulators"]: baud=modulator_config["baud"] mode=modulator_config["mode"] diff --git a/voicechat_modem_dsp/encoders/__init__.py b/voicechat_modem_dsp/encoders/__init__.py index 3e7a264..1ca2049 100644 --- a/voicechat_modem_dsp/encoders/__init__.py +++ b/voicechat_modem_dsp/encoders/__init__.py @@ -4,15 +4,15 @@ from .bitstream import readable_bytearr # Convenience mapping to allow for lookup based on len(modulation_list) -encode_function_mappings: Dict[int,Callable[[readable_bytearr], List[int]]]= \ - {2:base_2_encode, 4:base_4_encode, - 8:base_8_encode, 16:base_16_encode, - 32:base_32_encode, 64:base_64_encode, - 256:base_256_encode} -decode_function_mappings: Dict[int,Callable[[List[int]], readable_bytearr]]= \ - {2:base_2_decode, 4:base_4_decode, - 8:base_8_decode, 16:base_16_decode, - 32:base_32_decode, 64:base_64_decode, - 256:base_256_decode} +encode_function_mappings = {2:base_2_encode, 4:base_4_encode, + 8:base_8_encode, 16:base_16_encode, + 32:base_32_encode, 64:base_64_encode, + 256:base_256_encode + } # type: Dict[int,Callable[[readable_bytearr], List[int]]] +decode_function_mappings= {2:base_2_decode, 4:base_4_decode, + 8:base_8_decode, 16:base_16_decode, + 32:base_32_decode, 64:base_64_decode, + 256:base_256_decode + } # type: Dict[int,Callable[[List[int]], readable_bytearr]] __all__=["encode_function_mappings","decode_function_mappings"] From 27d19ba5336c58b8f7a8cda4071a34c8c7101479 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 16 Jan 2020 14:34:45 -0800 Subject: [PATCH 159/228] Start writing tests for new CLI elements Start with config_loader stuff and extend out --- test/cli/test_yaml_loader.py | 6 +++++- voicechat_modem_dsp/cli/config_loader.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/cli/test_yaml_loader.py b/test/cli/test_yaml_loader.py index 70be32b..4f2c18a 100644 --- a/test/cli/test_yaml_loader.py +++ b/test/cli/test_yaml_loader.py @@ -1,4 +1,5 @@ -from voicechat_modem_dsp.cli.config_loader import parse_config_str +from voicechat_modem_dsp.cli.config_loader import parse_config_str, \ + construct_modulators import glob import os @@ -23,7 +24,10 @@ def test_load_doc_examples(): with open(yaml_file,"r") as fil: config_text=fil.read() # Just make sure this doesn't cause an error + # TODO: other tests that check specific attributes config_obj=parse_config_str(config_text) + modulator_list=construct_modulators(config_obj.data) + assert len(modulator_list)==1 @pytest.mark.unit def test_load_doc_nonexamples(): diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index 92e927d..986a97c 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -83,6 +83,6 @@ def construct_modulators(config_dict: TypingMap) -> TypingList[Modulator]: frequencies=modulator_config["frequencies"] modulator_list.append( FSKModulator(fs, amplitude, frequencies, baud)) - else: + else: # Should never happen (would have been caught by revalidation) raise ValueError("Mapping has invalid mode key") return modulator_list From 070febfeaa7fbdc4127795c7f26a3ae7fff85970 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 16 Jan 2020 14:57:19 -0800 Subject: [PATCH 160/228] Add some # pragma: no cover annotations and ignore __main__.py --- .coveragerc | 4 +++- voicechat_modem_dsp/cli/command_objects.py | 4 ++-- voicechat_modem_dsp/cli/config_loader.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.coveragerc b/.coveragerc index daa431c..e95bab6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,4 @@ [run] -omit=*_base.py +omit= + *_base.py + voicechat_modem_dsp/__main__.py diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py index e2f6d32..1f03b4a 100644 --- a/voicechat_modem_dsp/cli/command_objects.py +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -109,7 +109,7 @@ def handle(self): bitstream=hamming_7_4.hamming_encode_7_4(bitstream) else: # Should never happen - raise CLIError("Invalid ECC mode found late; should have been caught earlier","error") + raise CLIError("Invalid ECC mode found late; should have been caught earlier","error") # pragma: no cover datastream=datastream_encoder(bitstream) self.line("Modulating data...") modulated_datastream=modulator_obj.modulate(datastream) @@ -196,7 +196,7 @@ def handle(self): bitstream=hamming_7_4.hamming_decode_7_4(bitstream) else: # Should never happen - raise CLIError("Invalid ECC mode found late; should have been caught earlier","error") + raise CLIError("Invalid ECC mode found late; should have been caught earlier","error") # pragma: no cover with open(output_file_name,"wb") as fil: fil.write(bitstream) diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index 986a97c..a7abbb2 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -84,5 +84,5 @@ def construct_modulators(config_dict: TypingMap) -> TypingList[Modulator]: modulator_list.append( FSKModulator(fs, amplitude, frequencies, baud)) else: # Should never happen (would have been caught by revalidation) - raise ValueError("Mapping has invalid mode key") + raise ValueError("Mapping has invalid mode key") # pragma: no cover return modulator_list From cbfd43034b32d31c096e5b265b5f27e400bf56ef Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 17 Jan 2020 19:35:30 -0800 Subject: [PATCH 161/228] Add newlines to end of files that were lacking them --- test/cli/test_custom_validators.py | 2 +- test/cli/test_yaml_loader.py | 2 +- voicechat_modem_dsp/__main__.py | 2 +- voicechat_modem_dsp/cli/yaml_schema_validators.py | 2 +- voicechat_modem_dsp/encoders/ecc/__init__.py | 2 +- voicechat_modem_dsp/modulators/__init__.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/cli/test_custom_validators.py b/test/cli/test_custom_validators.py index a0cd22b..da9855d 100644 --- a/test/cli/test_custom_validators.py +++ b/test/cli/test_custom_validators.py @@ -25,4 +25,4 @@ def test_complex_invalid(): yaml_obj=load(yaml_str_garbage,schema=complex_schema) yaml_str_bad_paren=generate_yaml("(aa,bb)") with pytest.raises(YAMLValidationError): - yaml_obj=load(yaml_str_bad_paren,schema=complex_schema) \ No newline at end of file + yaml_obj=load(yaml_str_bad_paren,schema=complex_schema) diff --git a/test/cli/test_yaml_loader.py b/test/cli/test_yaml_loader.py index 4f2c18a..d08556e 100644 --- a/test/cli/test_yaml_loader.py +++ b/test/cli/test_yaml_loader.py @@ -38,4 +38,4 @@ def test_load_doc_nonexamples(): # Just make sure this causes an error # See TODO notice as to why not check error message contents with pytest.raises(YAMLValidationError): - config_obj=parse_config_str(config_text) \ No newline at end of file + config_obj=parse_config_str(config_text) diff --git a/voicechat_modem_dsp/__main__.py b/voicechat_modem_dsp/__main__.py index 433a40c..d0a5ede 100644 --- a/voicechat_modem_dsp/__main__.py +++ b/voicechat_modem_dsp/__main__.py @@ -8,4 +8,4 @@ app.add(RxFile()) if __name__=="__main__": - app.run() \ No newline at end of file + app.run() diff --git a/voicechat_modem_dsp/cli/yaml_schema_validators.py b/voicechat_modem_dsp/cli/yaml_schema_validators.py index 8dc2801..4499896 100644 --- a/voicechat_modem_dsp/cli/yaml_schema_validators.py +++ b/voicechat_modem_dsp/cli/yaml_schema_validators.py @@ -30,4 +30,4 @@ def validate_scalar(self, chunk): except ValueError: chunk.expecting_but_found("when expecting a complex number") else: - chunk.expecting_but_found("when expecting a complex number") \ No newline at end of file + chunk.expecting_but_found("when expecting a complex number") diff --git a/voicechat_modem_dsp/encoders/ecc/__init__.py b/voicechat_modem_dsp/encoders/ecc/__init__.py index 2326038..3cbc406 100644 --- a/voicechat_modem_dsp/encoders/ecc/__init__.py +++ b/voicechat_modem_dsp/encoders/ecc/__init__.py @@ -1,3 +1,3 @@ from .hamming_7_4 import hamming_encode_7_4, hamming_decode_7_4 -__all__=["hamming_encode_7_4","hamming_decode_7_4"] \ No newline at end of file +__all__=["hamming_encode_7_4","hamming_decode_7_4"] diff --git a/voicechat_modem_dsp/modulators/__init__.py b/voicechat_modem_dsp/modulators/__init__.py index 5203112..cc6c6e2 100644 --- a/voicechat_modem_dsp/modulators/__init__.py +++ b/voicechat_modem_dsp/modulators/__init__.py @@ -6,4 +6,4 @@ _public_interface=[ASKModulator, FSKModulator, Modulator, ModulationIntegrityWarning] -__all__=[type_obj.__name__ for type_obj in _public_interface] \ No newline at end of file +__all__=[type_obj.__name__ for type_obj in _public_interface] From f0aaa3df08c8feb8447279065853237467b1dad8 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 17 Jan 2020 19:42:13 -0800 Subject: [PATCH 162/228] Remove unnecessary pass statements from empty classes with docstrings --- voicechat_modem_dsp/cli/command_utils.py | 2 -- voicechat_modem_dsp/modulators/modulator_utils.py | 1 - 2 files changed, 3 deletions(-) diff --git a/voicechat_modem_dsp/cli/command_utils.py b/voicechat_modem_dsp/cli/command_utils.py index a9ecc4d..b9e638d 100644 --- a/voicechat_modem_dsp/cli/command_utils.py +++ b/voicechat_modem_dsp/cli/command_utils.py @@ -6,7 +6,6 @@ class CLIError(Exception): """ Error raised to exit from cleo.Command.handle early """ - pass class FileExistsAndCannotOverwriteException(CLIError): """ @@ -14,7 +13,6 @@ class FileExistsAndCannotOverwriteException(CLIError): Subclass of CLIError """ - pass class ExtendedCommand(cleo.Command): """cleo.Command extended with additional utility functions""" diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 5c69ff2..7608f75 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -6,7 +6,6 @@ class ModulationIntegrityWarning(UserWarning): """ Warning to be raised when modulators cannot guarantee data accuracy """ - pass def generate_timearray(fs, sample_count): """ From 4e61bf7e37b81c3fa8dc37e3d2e2bdb1539c18a0 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 17 Jan 2020 20:13:20 -0800 Subject: [PATCH 163/228] Refactor config file reading in Command objects into base class --- voicechat_modem_dsp/cli/command_objects.py | 20 +++----------------- voicechat_modem_dsp/cli/command_utils.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py index 1f03b4a..de77f14 100644 --- a/voicechat_modem_dsp/cli/command_objects.py +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -4,9 +4,7 @@ import os from scipy.io import wavfile -from strictyaml import YAMLValidationError - -from .config_loader import parse_config_str, construct_modulators +from .config_loader import construct_modulators from .command_utils import CLIError, ExtendedCommand from ..modulators import ASKModulator, FSKModulator @@ -69,13 +67,7 @@ def handle(self): # Read in config file and check its validity self.line("Reading config file...") - try: - with open(config_file_name, "r") as fil: - config_text=fil.read() - config_obj=parse_config_str(config_text) - except YAMLValidationError as e: - # e.args[0] is the error message - raise CLIError(e.args[0],"error") + config_obj=self.parse_config_file(config_file_name) # TODO: obviously temporary; fix once prerequisites are done if len(config_obj["modulators"])>1: @@ -153,13 +145,7 @@ def handle(self): raise CLIError("Configuration file {} does not exist." .format(config_file_name),"error") - try: - with open(config_file_name, "r") as fil: - config_text=fil.read() - config_obj=parse_config_str(config_text) - except YAMLValidationError as e: - # e.args[0] is the error message - raise CLIError(e.args[0],"error") + config_obj=self.parse_config_file(config_file_name) # TODO: obviously temporary; fix once prerequisites are done if len(config_obj["modulators"])>1: diff --git a/voicechat_modem_dsp/cli/command_utils.py b/voicechat_modem_dsp/cli/command_utils.py index b9e638d..6320c2d 100644 --- a/voicechat_modem_dsp/cli/command_utils.py +++ b/voicechat_modem_dsp/cli/command_utils.py @@ -1,7 +1,10 @@ import cleo +from strictyaml import YAMLValidationError import os.path +from .config_loader import parse_config_str + class CLIError(Exception): """ Error raised to exit from cleo.Command.handle early @@ -37,3 +40,14 @@ def confirm_file_writable(self, filename: str) -> None: "Output file {} already exists " "and program is in noninteractive mode." .format(filename),"error") + + @staticmethod + def parse_config_file(config_file_name): + try: + with open(config_file_name, "r") as fil: + config_text=fil.read() + config_obj=parse_config_str(config_text) + except YAMLValidationError as e: + # e.args[0] is the error message + raise CLIError(e.args[0],"error") + return config_obj From 218a55d2fa8bac180f7995218aa3c2ea7f975b91 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Fri, 17 Jan 2020 20:43:41 -0800 Subject: [PATCH 164/228] Move decorator helpers to testing_utils.py --- test/cli/test_yaml_loader.py | 12 +----------- test/cli/testing_utils.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 test/cli/testing_utils.py diff --git a/test/cli/test_yaml_loader.py b/test/cli/test_yaml_loader.py index d08556e..0947c72 100644 --- a/test/cli/test_yaml_loader.py +++ b/test/cli/test_yaml_loader.py @@ -1,22 +1,12 @@ from voicechat_modem_dsp.cli.config_loader import parse_config_str, \ construct_modulators +from .testing_utils import DirectoryChanger, FileCleanup import glob -import os from strictyaml import YAMLValidationError import pytest -class DirectoryChanger: - def __init__(self, path): - self.oldpath=os.getcwd() - self.path=path - - def __enter__(self): - os.chdir(self.path) - def __exit__(self, exc_type, exc_val, exc_tb): - os.chdir(self.oldpath) - @pytest.mark.unit def test_load_doc_examples(): with DirectoryChanger("docs/specs/examples"): diff --git a/test/cli/testing_utils.py b/test/cli/testing_utils.py new file mode 100644 index 0000000..7103d25 --- /dev/null +++ b/test/cli/testing_utils.py @@ -0,0 +1,21 @@ +import os + +class DirectoryChanger: + def __init__(self, path): + self.oldpath=os.getcwd() + self.path=path + + def __enter__(self): + os.chdir(self.path) + def __exit__(self, exc_type, exc_val, exc_tb): + os.chdir(self.oldpath) + +class FileCleanup: + def __init__(self, filename): + self.filename = filename + + def __enter__(self): + assert not os.path.exists(self.filename) + def __exit__(self): + assert os.path.isfile(self.filename) + os.remove(self.filename) \ No newline at end of file From 21b05e0b53fdf1911593f5f7f3836ed7c789d878 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 18 Jan 2020 23:15:43 -0800 Subject: [PATCH 165/228] Prepend fsk to bell_202.yaml example's file name --- docs/specs/config.rst | 2 +- docs/specs/examples/{bell_202.yaml => fsk_bell_202.yaml} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename docs/specs/examples/{bell_202.yaml => fsk_bell_202.yaml} (100%) diff --git a/docs/specs/config.rst b/docs/specs/config.rst index b4b52e1..0b1128e 100644 --- a/docs/specs/config.rst +++ b/docs/specs/config.rst @@ -82,7 +82,7 @@ Example Files `Bell 202 `_ FSK Modulation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. literalinclude:: examples/bell_202.yaml +.. literalinclude:: examples/fsk_bell_202.yaml :language: yaml 1024 bits/second ASK Modulation diff --git a/docs/specs/examples/bell_202.yaml b/docs/specs/examples/fsk_bell_202.yaml similarity index 100% rename from docs/specs/examples/bell_202.yaml rename to docs/specs/examples/fsk_bell_202.yaml From b89a6fdd5d7593f4dc9a724ae4a6c152a7b9b50e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 18 Jan 2020 23:15:52 -0800 Subject: [PATCH 166/228] testing_utils.py newline at end --- test/cli/testing_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli/testing_utils.py b/test/cli/testing_utils.py index 7103d25..b826bca 100644 --- a/test/cli/testing_utils.py +++ b/test/cli/testing_utils.py @@ -18,4 +18,4 @@ def __enter__(self): assert not os.path.exists(self.filename) def __exit__(self): assert os.path.isfile(self.filename) - os.remove(self.filename) \ No newline at end of file + os.remove(self.filename) From 9796f6df36707f1d6aea1f4988d84b0bcaa8a1bc Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 18 Jan 2020 23:16:10 -0800 Subject: [PATCH 167/228] Check file name and test that right modulator is present, when possible --- test/cli/test_yaml_loader.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/cli/test_yaml_loader.py b/test/cli/test_yaml_loader.py index 0947c72..2c772dc 100644 --- a/test/cli/test_yaml_loader.py +++ b/test/cli/test_yaml_loader.py @@ -1,5 +1,6 @@ from voicechat_modem_dsp.cli.config_loader import parse_config_str, \ construct_modulators +from voicechat_modem_dsp.modulators import ASKModulator, FSKModulator from .testing_utils import DirectoryChanger, FileCleanup import glob @@ -13,11 +14,21 @@ def test_load_doc_examples(): for yaml_file in glob.iglob("*.yaml"): with open(yaml_file,"r") as fil: config_text=fil.read() - # Just make sure this doesn't cause an error - # TODO: other tests that check specific attributes + # YAML is valid <=> no error in parsing config_obj=parse_config_str(config_text) modulator_list=construct_modulators(config_obj.data) + + # TODO: remove length check once FDM is in place assert len(modulator_list)==1 + # TODO: complete for other modes once done + if "ask" in yaml_file: + assert isinstance(modulator_list[0],ASKModulator) + elif "fsk" in yaml_file: + assert isinstance(modulator_list[0],FSKModulator) + elif "psk" in yaml_file: + pass + elif "qam" in yaml_file: + pass @pytest.mark.unit def test_load_doc_nonexamples(): From d216957f1b50ffa3cb7e7bc68e525117adb21434 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 18 Jan 2020 23:28:27 -0800 Subject: [PATCH 168/228] Copy yaml loading tests but use ExtendedCommand static methods this time --- test/cli/test_yaml_loader.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/cli/test_yaml_loader.py b/test/cli/test_yaml_loader.py index 2c772dc..5b28fb1 100644 --- a/test/cli/test_yaml_loader.py +++ b/test/cli/test_yaml_loader.py @@ -1,5 +1,6 @@ from voicechat_modem_dsp.cli.config_loader import parse_config_str, \ construct_modulators +from voicechat_modem_dsp.cli.command_utils import CLIError, ExtendedCommand from voicechat_modem_dsp.modulators import ASKModulator, FSKModulator from .testing_utils import DirectoryChanger, FileCleanup @@ -30,6 +31,26 @@ def test_load_doc_examples(): elif "qam" in yaml_file: pass +@pytest.mark.unit +def test_load_doc_examples_staticmethod(): + with DirectoryChanger("docs/specs/examples"): + for yaml_file in glob.iglob("*.yaml"): + # YAML is valid <=> no error in parsing + config_obj=ExtendedCommand.parse_config_file(yaml_file) + modulator_list=construct_modulators(config_obj.data) + + # TODO: remove length check once FDM is in place + assert len(modulator_list)==1 + # TODO: complete for other modes once done + if "ask" in yaml_file: + assert isinstance(modulator_list[0],ASKModulator) + elif "fsk" in yaml_file: + assert isinstance(modulator_list[0],FSKModulator) + elif "psk" in yaml_file: + pass + elif "qam" in yaml_file: + pass + @pytest.mark.unit def test_load_doc_nonexamples(): with DirectoryChanger("docs/specs/nonexamples"): @@ -40,3 +61,12 @@ def test_load_doc_nonexamples(): # See TODO notice as to why not check error message contents with pytest.raises(YAMLValidationError): config_obj=parse_config_str(config_text) + +@pytest.mark.unit +def test_load_doc_nonexamples_staticmethod(): + with DirectoryChanger("docs/specs/nonexamples"): + for yaml_file in glob.iglob("*.yaml"): + # Just make sure this causes an error + # See TODO notice as to why not check error message contents + with pytest.raises(CLIError): + config_obj=ExtendedCommand.parse_config_file(yaml_file) From de3ef1128fba7eb383967c149bf43852a1408839 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 19 Jan 2020 13:36:21 -0800 Subject: [PATCH 169/228] Finish writing tests for ExtendedCommand helpers --- test/cli/test_commands.py | 51 ++++++++++++++++++++++++ test/cli/testing_utils.py | 11 +++++ voicechat_modem_dsp/cli/command_utils.py | 6 ++- 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 test/cli/test_commands.py diff --git a/test/cli/test_commands.py b/test/cli/test_commands.py new file mode 100644 index 0000000..7eded31 --- /dev/null +++ b/test/cli/test_commands.py @@ -0,0 +1,51 @@ +import cleo + +from voicechat_modem_dsp.cli.command_utils import \ + ExtendedCommand, FileExistsAndCannotOverwriteException + +from .testing_utils import MockIO +import os + +import pytest + +@pytest.mark.unit +def test_extendedcmd_noninteractive(): + extended_cmd = ExtendedCommand() + extended_cmd._io = MockIO(is_interactive=False,input_list=list()) + + # Can write to nonexistent file + extended_cmd.confirm_file_writable("garbage") + # Cannot write to existent file in noninteractive mode + with pytest.raises(FileExistsAndCannotOverwriteException): + extended_cmd.confirm_file_writable("__init__.py") + + # Cannot write to directory + try: + os.mkdir("nonsense") + with pytest.raises(FileExistsAndCannotOverwriteException): + extended_cmd.confirm_file_writable("nonsense") + finally: + os.rmdir("nonsense") + +@pytest.mark.unit +def test_file_extendedcmd_interactive(): + extended_cmd = ExtendedCommand() + extended_cmd._io = MockIO(is_interactive=True, input_list=[False]) + + # Can write to nonexistent file + extended_cmd.confirm_file_writable("garbage") + # Cannot write to directory + try: + os.mkdir("nonsense") + with pytest.raises(FileExistsAndCannotOverwriteException): + extended_cmd.confirm_file_writable("nonsense") + finally: + os.rmdir("nonsense") + + # Cannot write to existent file when nonconfirmed + with pytest.raises(FileExistsAndCannotOverwriteException): + extended_cmd.confirm_file_writable("__init__.py") + + # Can write to existent file when confirmed + extended_cmd._io = MockIO(is_interactive=True, input_list=[True]) + extended_cmd.confirm_file_writable("__init__.py") diff --git a/test/cli/testing_utils.py b/test/cli/testing_utils.py index b826bca..83f98cf 100644 --- a/test/cli/testing_utils.py +++ b/test/cli/testing_utils.py @@ -19,3 +19,14 @@ def __enter__(self): def __exit__(self): assert os.path.isfile(self.filename) os.remove(self.filename) + +class MockIO: + def __init__(self, *, is_interactive, input_list): + self.has_interactive=is_interactive + self.input_list=input_list.copy() + + def is_interactive(self): + return self.has_interactive + + def confirm(self, question, default, true_answer_regex): + return self.input_list.pop(0) \ No newline at end of file diff --git a/voicechat_modem_dsp/cli/command_utils.py b/voicechat_modem_dsp/cli/command_utils.py index 6320c2d..3ec7b10 100644 --- a/voicechat_modem_dsp/cli/command_utils.py +++ b/voicechat_modem_dsp/cli/command_utils.py @@ -18,7 +18,11 @@ class FileExistsAndCannotOverwriteException(CLIError): """ class ExtendedCommand(cleo.Command): - """cleo.Command extended with additional utility functions""" + """ + cleo.Command extended with additional utility functions + + abstract_should_not_be_seen + """ """Confirm whether a file is writable and raise an exception if not""" def confirm_file_writable(self, filename: str) -> None: From 8c64265e7bc2790183851489beb77f76aedb7bde Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 20 Jan 2020 09:43:16 -0500 Subject: [PATCH 170/228] Add missing args to FileCleanup __exit__ method --- test/cli/testing_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli/testing_utils.py b/test/cli/testing_utils.py index 83f98cf..65ff2be 100644 --- a/test/cli/testing_utils.py +++ b/test/cli/testing_utils.py @@ -16,7 +16,7 @@ def __init__(self, filename): def __enter__(self): assert not os.path.exists(self.filename) - def __exit__(self): + def __exit__(self, exc_type, exc_val, exc_tb): assert os.path.isfile(self.filename) os.remove(self.filename) From 0fb97a52e3cfb7020f6fa90f48cf49033fe4a1d8 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 20 Jan 2020 09:43:29 -0500 Subject: [PATCH 171/228] More tests for CLI interface Good enough for now --- test/cli/test_commands.py | 74 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/test/cli/test_commands.py b/test/cli/test_commands.py index 7eded31..4cec9a8 100644 --- a/test/cli/test_commands.py +++ b/test/cli/test_commands.py @@ -2,8 +2,8 @@ from voicechat_modem_dsp.cli.command_utils import \ ExtendedCommand, FileExistsAndCannotOverwriteException - -from .testing_utils import MockIO +from voicechat_modem_dsp.cli.command_objects import TxFile, RxFile +from .testing_utils import MockIO, FileCleanup import os import pytest @@ -28,7 +28,7 @@ def test_extendedcmd_noninteractive(): os.rmdir("nonsense") @pytest.mark.unit -def test_file_extendedcmd_interactive(): +def test_extendedcmd_interactive(): extended_cmd = ExtendedCommand() extended_cmd._io = MockIO(is_interactive=True, input_list=[False]) @@ -49,3 +49,71 @@ def test_file_extendedcmd_interactive(): # Can write to existent file when confirmed extended_cmd._io = MockIO(is_interactive=True, input_list=[True]) extended_cmd.confirm_file_writable("__init__.py") + +@pytest.mark.unit +def test_roundtrip_modulation_cmd(): + with FileCleanup("modulated.wav"): + with FileCleanup("demodulated.dat"): + application = cleo.Application() + application.add(TxFile()) + application.add(RxFile()) + tx_commands=" ".join(["--config docs/specs/examples/ask_1k.yaml", + "--output modulated.wav","--raw","test/cli/testing_utils.py"]) + + command_tx = application.find("transmit_file") + command_tx_tester = cleo.CommandTester(command_tx) + command_tx_tester.execute(tx_commands) + + assert command_tx_tester.status_code == 0 + + rx_commands=" ".join(["--config docs/specs/examples/ask_1k.yaml", + "--output demodulated.dat","modulated.wav"]) + command_rx = application.find("receive_file") + command_rx_tester = cleo.CommandTester(command_rx) + command_rx_tester.execute(rx_commands) + + assert command_rx_tester.status_code == 0 + +@pytest.mark.unit +def test_improper_file_parameters(): + application = cleo.Application() + application.add(TxFile()) + application.add(RxFile()) + tx_command_no_config=" ".join(["--output modulated.wav","--raw", + "test/cli/testing_utils.py"]) + tx_command_config_nonexist=" ".join(["--output modulated.wav","--raw", + "--config nonexist","test/cli/testing_utils.py"]) + tx_command_input_nonexist=" ".join([ + "--config docs/specs/examples/ask_1k.yaml", "--output modulated.wav", + "--raw","nonexist"]) + + command_tx = application.find("transmit_file") + command_tx_tester = cleo.CommandTester(command_tx) + + command_tx_tester.execute(tx_command_no_config) + assert command_tx_tester.status_code == 1 + + command_tx_tester.execute(tx_command_config_nonexist) + assert command_tx_tester.status_code == 1 + + command_tx_tester.execute(tx_command_input_nonexist) + assert command_tx_tester.status_code == 1 + + rx_command_no_config=" ".join(["--output demodulated.dat","input.wav"]) + rx_command_config_nonexist=" ".join(["--output demodulated.dat", + "--config nonexist","input.wav"]) + rx_command_input_nonexist=" ".join([ + "--config docs/specs/examples/ask_1k.yaml", "--output demodulated.dat", + "nonexist.wav"]) + + command_rx = application.find("receive_file") + command_rx_tester = cleo.CommandTester(command_rx) + + command_rx_tester.execute(rx_command_no_config) + assert command_rx_tester.status_code == 1 + + command_rx_tester.execute(rx_command_config_nonexist) + assert command_rx_tester.status_code == 1 + + command_rx_tester.execute(rx_command_input_nonexist) + assert command_rx_tester.status_code == 1 From 457bbd8e47c4dc1d2a704ebdc7359dd08c244cf7 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 20 Jan 2020 21:53:29 -0500 Subject: [PATCH 172/228] Loosen type signatures for datastream decode functions --- voicechat_modem_dsp/encoders/__init__.py | 4 ++-- voicechat_modem_dsp/encoders/encode_pad.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/voicechat_modem_dsp/encoders/__init__.py b/voicechat_modem_dsp/encoders/__init__.py index 1ca2049..0509deb 100644 --- a/voicechat_modem_dsp/encoders/__init__.py +++ b/voicechat_modem_dsp/encoders/__init__.py @@ -1,6 +1,6 @@ from .encode_pad import * -from typing import Dict, Callable, List +from typing import Dict, Callable, Sequence from .bitstream import readable_bytearr # Convenience mapping to allow for lookup based on len(modulation_list) @@ -13,6 +13,6 @@ 8:base_8_decode, 16:base_16_decode, 32:base_32_decode, 64:base_64_decode, 256:base_256_decode - } # type: Dict[int,Callable[[List[int]], readable_bytearr]] + } # type: Dict[int,Callable[[Sequence[int]], readable_bytearr]] __all__=["encode_function_mappings","decode_function_mappings"] diff --git a/voicechat_modem_dsp/encoders/encode_pad.py b/voicechat_modem_dsp/encoders/encode_pad.py index ab369ab..ccaa393 100644 --- a/voicechat_modem_dsp/encoders/encode_pad.py +++ b/voicechat_modem_dsp/encoders/encode_pad.py @@ -1,7 +1,7 @@ from .bitstream import read_bitstream_iterator, write_bitstream from .bitstream import readable_bytearr, writeable_bytearr -from typing import List, Dict, Callable +from typing import List, Sequence, Dict, Callable import base64 @@ -12,7 +12,7 @@ def base_2_encode(bitstream: readable_bytearr) -> List[int]: return_list.append(1 if bit else 0) return return_list -def base_2_decode(datastream: List[int]) -> readable_bytearr: +def base_2_decode(datastream: Sequence[int]) -> readable_bytearr: base_2_mapping={0:False, 1:True} if len(datastream)%8 != 0: raise ValueError("Inappropriate datastream length") @@ -38,7 +38,7 @@ def base_4_encode(bitstream: readable_bytearr) -> List[int]: emit_symbol=not emit_symbol return return_list -def base_4_decode(datastream: List[int]) -> readable_bytearr: +def base_4_decode(datastream: Sequence[int]) -> readable_bytearr: base_4_mapping={0:(False,False), 1:(False,True), 2:(True,False), @@ -77,7 +77,7 @@ def base_8_encode(bitstream: readable_bytearr) -> List[int]: return_list.append(rollover_counter) return return_list -def base_8_decode(datastream: List[int]) -> readable_bytearr: +def base_8_decode(datastream: Sequence[int]) -> readable_bytearr: base_8_mapping={0:(False,False,False), 1:(False,False,True), 2:(False,True,False), @@ -110,7 +110,7 @@ def base_8_decode(datastream: List[int]) -> readable_bytearr: def base_16_encode(bitstream: readable_bytearr) -> List[int]: return [int(chr(c), 16) for c in base64.b16encode(bitstream)] -def base_16_decode(datastream: List[int]) -> readable_bytearr: +def base_16_decode(datastream: Sequence[int]) -> readable_bytearr: if len(datastream)%2!=0: raise ValueError("Inappropriate datastream length") for c in datastream: @@ -129,7 +129,7 @@ def base_32_encode(bitstream: readable_bytearr) -> List[int]: # b32encode works properly -> KeyError impossible return [base_32_mapping[char] for char in base_32_encodestr] -def base_32_decode(datastream: List[int]) -> readable_bytearr: +def base_32_decode(datastream: Sequence[int]) -> readable_bytearr: # Convert to dict to avoid negative index wrapping base_32_mapping=dict(enumerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")) try: @@ -154,7 +154,7 @@ def base_64_encode(bitstream: readable_bytearr) -> List[int]: # b32encode works properly -> KeyError impossible return [base_64_mapping[char] for char in base_64_encodestr] -def base_64_decode(datastream: List[int]) -> readable_bytearr: +def base_64_decode(datastream: Sequence[int]) -> readable_bytearr: # Convert to dict to avoid negative index wrapping base_64_mapping=dict(enumerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz0123456789+/")) @@ -172,7 +172,7 @@ def base_64_decode(datastream: List[int]) -> readable_bytearr: def base_256_encode(bitstream: readable_bytearr) -> List[int]: return list(bitstream) -def base_256_decode(datastream: List[int]) -> readable_bytearr: +def base_256_decode(datastream: Sequence[int]) -> readable_bytearr: # Catch ValueError to provide our own message here try: return bytes(datastream) From 077a4fb8662ad0a0edbc440d25b5e81dea8bbb6f Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 21 Jan 2020 10:29:21 -0500 Subject: [PATCH 173/228] Adjust whitespace of testing_utils.py --- test/cli/testing_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/cli/testing_utils.py b/test/cli/testing_utils.py index 65ff2be..efe58dd 100644 --- a/test/cli/testing_utils.py +++ b/test/cli/testing_utils.py @@ -4,7 +4,7 @@ class DirectoryChanger: def __init__(self, path): self.oldpath=os.getcwd() self.path=path - + def __enter__(self): os.chdir(self.path) def __exit__(self, exc_type, exc_val, exc_tb): @@ -24,9 +24,9 @@ class MockIO: def __init__(self, *, is_interactive, input_list): self.has_interactive=is_interactive self.input_list=input_list.copy() - + def is_interactive(self): return self.has_interactive - def confirm(self, question, default, true_answer_regex): - return self.input_list.pop(0) \ No newline at end of file + return self.input_list.pop(0) + From 613cff18812cab951b31e3ac4ae723dc6c1a2e83 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 26 Jan 2020 14:40:34 -0500 Subject: [PATCH 174/228] Modify ASK integrity test to ensure phase independence --- test/modulators/test_modulator_integrity.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/modulators/test_modulator_integrity.py b/test/modulators/test_modulator_integrity.py index b445969..49aa221 100644 --- a/test/modulators/test_modulator_integrity.py +++ b/test/modulators/test_modulator_integrity.py @@ -1,6 +1,7 @@ import random import warnings import numpy as np +from scipy.signal import hilbert from voicechat_modem_dsp.encoders.encode_pad import * from voicechat_modem_dsp.modulators import ASKModulator, FSKModulator, \ @@ -113,8 +114,12 @@ def test_property_ask_integrity(): continue modulated_data=modulator.modulate(datastream) - modulator.demodulate(modulated_data) - demodulated_bundle=modulator.demodulate(modulated_data) + + modulated_data_analytic=hilbert(modulated_data) + modulated_data_analytic*=np.exp(2j*np.pi*random.random()) + modulated_data_phaseshifted=np.real(modulated_data_analytic) + + demodulated_bundle=modulator.demodulate(modulated_data_phaseshifted) recovered_bitstream=base_16_decode(demodulated_bundle) count_run+=1 From 4f0948ff272f836fea2853c9a235f663868dcb27 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 27 Jan 2020 17:31:05 -0500 Subject: [PATCH 175/228] Rename base Modulator class to BaseModulator --- voicechat_modem_dsp/cli/config_loader.py | 6 +++--- voicechat_modem_dsp/modulators/__init__.py | 4 ++-- voicechat_modem_dsp/modulators/modulator_ask.py | 10 +++++----- voicechat_modem_dsp/modulators/modulator_ask.pyi | 4 ++-- voicechat_modem_dsp/modulators/modulator_base.py | 2 +- voicechat_modem_dsp/modulators/modulator_fsk.py | 11 ++++++----- voicechat_modem_dsp/modulators/modulator_fsk.pyi | 4 ++-- 7 files changed, 21 insertions(+), 20 deletions(-) diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index a7abbb2..3382253 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -3,7 +3,7 @@ from strictyaml import load, YAML, YAMLValidationError from .yaml_schema_validators import Complex -from ..modulators import ASKModulator, FSKModulator, Modulator +from ..modulators import ASKModulator, FSKModulator, BaseModulator from typing import List as TypingList from typing import Mapping as TypingMap @@ -64,9 +64,9 @@ def parse_config_str(string: str) -> YAML: "in modulator list",None,modulator) return config_obj -def construct_modulators(config_dict: TypingMap) -> TypingList[Modulator]: +def construct_modulators(config_dict: TypingMap) -> TypingList[BaseModulator]: fs=config_dict["fs"] - modulator_list=list() # type: TypingList[Modulator] + modulator_list=list() # type: TypingList[BaseModulator] for modulator_config in config_dict["modulators"]: baud=modulator_config["baud"] mode=modulator_config["mode"] diff --git a/voicechat_modem_dsp/modulators/__init__.py b/voicechat_modem_dsp/modulators/__init__.py index cc6c6e2..0d5ee3a 100644 --- a/voicechat_modem_dsp/modulators/__init__.py +++ b/voicechat_modem_dsp/modulators/__init__.py @@ -1,9 +1,9 @@ from .modulator_ask import ASKModulator from .modulator_fsk import FSKModulator -from .modulator_base import Modulator +from .modulator_base import BaseModulator from .modulator_utils import ModulationIntegrityWarning -_public_interface=[ASKModulator, FSKModulator, Modulator, +_public_interface=[ASKModulator, FSKModulator, BaseModulator, ModulationIntegrityWarning] __all__=[type_obj.__name__ for type_obj in _public_interface] diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index e3f9cd2..499d170 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -1,4 +1,4 @@ -from .modulator_base import Modulator +from .modulator_base import BaseModulator from . import modulator_utils from .modulator_utils import ModulationIntegrityWarning @@ -9,7 +9,7 @@ import warnings -class ASKModulator(Modulator): +class ASKModulator(BaseModulator): def __init__(self, fs, carrier, amp_list, baud): if baud>=0.5*carrier: raise ValueError("Baud is too high to be modulated "+ @@ -42,13 +42,13 @@ def __init__(self, fs, carrier, amp_list, baud): @property def _calculate_sigma(self): #sigma_t = w/4k, at most half of the pulse is smoothed away - gaussian_sigma_t=(1/self.baud)/(4*Modulator.sigma_mult_t) + gaussian_sigma_t=(1/self.baud)/(4*BaseModulator.sigma_mult_t) # Ensure dropoff at halfway to doubled carrier frequency is -80dB # This is not the same as carrier_freq because Nyquist reflections doubled_carrier_refl=min( 2*self.carrier_freq,self.fs-2*self.carrier_freq) halfway_thresh=0.5*doubled_carrier_refl - gaussian_sigma_f=Modulator.sigma_mult_f/(2*np.pi*halfway_thresh) + gaussian_sigma_f=BaseModulator.sigma_mult_f/(2*np.pi*halfway_thresh) return min(gaussian_sigma_t,gaussian_sigma_f) def modulate(self, datastream): @@ -107,7 +107,7 @@ def demodulate(self, modulated_data): list_amplitudes=list() for i in range(interval_count): - transition_width=Modulator.sigma_mult_t*self._calculate_sigma + transition_width=BaseModulator.sigma_mult_t*self._calculate_sigma # Convert above time width into sample point width transition_width*=self.fs diff --git a/voicechat_modem_dsp/modulators/modulator_ask.pyi b/voicechat_modem_dsp/modulators/modulator_ask.pyi index b65e0de..c4a5f63 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.pyi +++ b/voicechat_modem_dsp/modulators/modulator_ask.pyi @@ -1,9 +1,9 @@ -from .modulator_base import Modulator +from .modulator_base import BaseModulator from typing import Sequence, Dict, Tuple from numpy import ndarray -class ASKModulator(Modulator): +class ASKModulator(BaseModulator): fs: float = ... amp_list: Dict[int, float] = ... carrier_freq: float = ... diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index 231e4af..2e661fd 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -8,7 +8,7 @@ Base class for other modulator objects Also contains useful helpers as static attributes """ -class Modulator(ABC): +class BaseModulator(ABC): # norm.isf(1/(2*2^8)) sigma_mult_t=2.89 # norm.isf(0.0001) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index 1d37ee0..55aca3b 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -1,4 +1,4 @@ -from .modulator_base import Modulator +from .modulator_base import BaseModulator from . import modulator_utils from .modulator_utils import ModulationIntegrityWarning @@ -10,7 +10,7 @@ import warnings -class FSKModulator(Modulator): +class FSKModulator(BaseModulator): def __init__(self, fs, amplitude, freq_list, baud): if min(freq_list)<=0: raise ValueError("Invalid frequencies given") @@ -42,10 +42,11 @@ def __init__(self, fs, amplitude, freq_list, baud): @property def _calculate_sigma(self): #sigma_t = w/4k, at most half of the pulse is smoothed away - gaussian_sigma_t=(1/self.baud)/(4*Modulator.sigma_mult_t) + gaussian_sigma_t=(1/self.baud)/(4*BaseModulator.sigma_mult_t) # At most 1/4 of the highest frequency cycle is smoothed away # TODO: test possibilities that make sense for FSK - gaussian_sigma_f=0.25*(2*np.pi/max(self.freq_list.values()))/Modulator.sigma_mult_t + gaussian_sigma_f=0.25*(2*np.pi/max(self.freq_list.values()))/ \ + BaseModulator.sigma_mult_t return min(gaussian_sigma_t,gaussian_sigma_f) def modulate(self, datastream): @@ -91,7 +92,7 @@ def demodulate(self, modulated_data): len(modulated_data)/samples_per_symbol)) goertzel_results=list() for i in range(interval_count): - transition_width=Modulator.sigma_mult_t*self._calculate_sigma + transition_width=BaseModulator.sigma_mult_t*self._calculate_sigma # Convert above time width into sample point width transition_width*=self.fs diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.pyi b/voicechat_modem_dsp/modulators/modulator_fsk.pyi index 01c15a7..80c2abe 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.pyi +++ b/voicechat_modem_dsp/modulators/modulator_fsk.pyi @@ -1,9 +1,9 @@ -from .modulator_base import Modulator +from .modulator_base import BaseModulator from typing import Sequence, Dict, Tuple from numpy import ndarray -class FSKModulator(Modulator): +class FSKModulator(BaseModulator): fs: float = ... freq_list: Dict[int, float] = ... amplitude: float = ... From 26d3b27d89988dfab7fe49e19edfeabcadd76509 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 27 Jan 2020 17:32:12 -0500 Subject: [PATCH 176/228] Explain random phase shift part of ASK property test --- test/modulators/test_modulator_integrity.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/modulators/test_modulator_integrity.py b/test/modulators/test_modulator_integrity.py index 49aa221..687b49a 100644 --- a/test/modulators/test_modulator_integrity.py +++ b/test/modulators/test_modulator_integrity.py @@ -115,6 +115,9 @@ def test_property_ask_integrity(): modulated_data=modulator.modulate(datastream) + # Imaginary part of analytic signal is phase shifted 90 degrees + # Randomize the phase of the modulated signal + # Demodulation should not care about signal phase modulated_data_analytic=hilbert(modulated_data) modulated_data_analytic*=np.exp(2j*np.pi*random.random()) modulated_data_phaseshifted=np.real(modulated_data_analytic) From 23d5d8d391db18aaccf76e169b4b7ddba22854b5 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 27 Jan 2020 17:32:31 -0500 Subject: [PATCH 177/228] Move transition width calculation out of for loop --- voicechat_modem_dsp/modulators/modulator_ask.py | 8 ++++---- voicechat_modem_dsp/modulators/modulator_fsk.py | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 499d170..8bdddb9 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -106,11 +106,11 @@ def demodulate(self, modulated_data): interval_offset=filt_delay list_amplitudes=list() - for i in range(interval_count): - transition_width=BaseModulator.sigma_mult_t*self._calculate_sigma - # Convert above time width into sample point width - transition_width*=self.fs + transition_width=BaseModulator.sigma_mult_t*self._calculate_sigma + # Convert above time width into sample point width + transition_width*=self.fs + for i in range(interval_count): interval_begin=interval_offset+i*samples_per_symbol # Perform min in order to account for floating point weirdness interval_end=min(interval_begin+samples_per_symbol, diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index 55aca3b..de48f6e 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -91,11 +91,12 @@ def demodulate(self, modulated_data): interval_count=int(np.round( len(modulated_data)/samples_per_symbol)) goertzel_results=list() - for i in range(interval_count): - transition_width=BaseModulator.sigma_mult_t*self._calculate_sigma - # Convert above time width into sample point width - transition_width*=self.fs + transition_width=BaseModulator.sigma_mult_t*self._calculate_sigma + # Convert above time width into sample point width + transition_width*=self.fs + + for i in range(interval_count): interval_begin=i*samples_per_symbol # Perform min in order to account for floating point weirdness interval_end=min(interval_begin+samples_per_symbol, From 4fe4efd8128d06c81112cf26bef5efea5e81c4f6 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 27 Jan 2020 17:37:03 -0500 Subject: [PATCH 178/228] Only skip Gaussian smoothing parts at endpoints for FSK Frequency shifts are instantaneous so this kind of skipping wastes data points --- voicechat_modem_dsp/modulators/modulator_fsk.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index de48f6e..adc3230 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -104,10 +104,10 @@ def demodulate(self, modulated_data): # Shrink interval by previously calculated transition width # Skip doing so for first and last sample - if i!=0: - interval_begin+=transition_width - if i!=interval_count-1: + if i==0 or i==interval_count-2: interval_end-=transition_width + if i==1 or i==interval_count-1: + interval_begin+=transition_width # Round transition boundaries to get integer indexes # TODO: find elegant way to handle noninteger interval bounds interval_begin=int(np.round(interval_begin)) From fb4c79fa4c47acbe1e79b7561a6b5a58fb5a625f Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 1 Feb 2020 14:10:53 -0500 Subject: [PATCH 179/228] Create modulator for PSK Demodulating PSK while correctly handling the null symbol is going to be harder --- .../modulators/modulator_psk.py | 161 ++++++++++++++++++ .../modulators/modulator_psk.pyi | 14 ++ 2 files changed, 175 insertions(+) create mode 100644 voicechat_modem_dsp/modulators/modulator_psk.py create mode 100644 voicechat_modem_dsp/modulators/modulator_psk.pyi diff --git a/voicechat_modem_dsp/modulators/modulator_psk.py b/voicechat_modem_dsp/modulators/modulator_psk.py new file mode 100644 index 0000000..b21ba09 --- /dev/null +++ b/voicechat_modem_dsp/modulators/modulator_psk.py @@ -0,0 +1,161 @@ +from .modulator_base import BaseModulator + +from . import modulator_utils +from .modulator_utils import ModulationIntegrityWarning + +import numpy as np +from scipy import signal +from scipy.cluster.vq import vq + +import warnings + +class PSKModulator(BaseModulator): + def __init__(self, fs, carrier, amplitude, phase_list, baud): + if baud>=0.5*carrier: + raise ValueError("Baud is too high to be modulated "+ + "using carrier frequency") + # Nyquist limit + if carrier>=0.5*fs: + raise ValueError("Carrier frequency is too high for sampling rate") + if any((x>=2*np.pi or x<0 for x in phase_list)): + raise ValueError("Invalid phases given") + + if amplitude>1 or amplitude<=0: + raise ValueError("Base amplitude must be positive and at most 1") + + self.amplitude=amplitude + self.fs=fs + self.phase_list=dict(enumerate(phase_list)) + self.carrier_freq=carrier + self.baud=baud + # Nyquist aliased 2*carrier=carrier + if carrier>=(1/3)*fs: + warnings.warn("Carrier frequency is too high to guarantee " + "proper lowpass reconstruction", + ModulationIntegrityWarning) + if amplitude<0.1: + warnings.warn("Amplitude may be too small to allow " + "reliable reconstruction",ModulationIntegrityWarning) + if any(dx<=(0.05*2*np.pi) for dx in np.diff(sorted(phase_list))): + warnings.warn("Phases may be too close " + "to be distinguishable from each other", + ModulationIntegrityWarning) + # TODO: additional warnings relating to filter overshoot and the like + + @property + def _calculate_sigma(self): + #sigma_t = w/4k, at most half of the pulse is smoothed away + gaussian_sigma_t=(1/self.baud)/(4*BaseModulator.sigma_mult_t) + # Ensure dropoff at halfway to doubled carrier frequency is -80dB + # This is not the same as carrier_freq because Nyquist reflections + doubled_carrier_refl=min( + 2*self.carrier_freq,self.fs-2*self.carrier_freq) + halfway_thresh=0.5*doubled_carrier_refl + gaussian_sigma_f=BaseModulator.sigma_mult_f/(2*np.pi*halfway_thresh) + return min(gaussian_sigma_t,gaussian_sigma_f) + + def modulate(self, datastream): + # TODO: complete + samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) + gaussian_sigma=self._calculate_sigma + gaussian_window=modulator_utils.gaussian_window(self.fs,gaussian_sigma) + + # Map datastream to phases and pad with 0 on both ends + phase_data = np.pad([self.phase_list[datum] for datum in datastream], + 1,mode="constant",constant_values=0) + + # Upsample amplitude to actual sampling rate + interp_sample_count=int(np.ceil( + len(phase_data)*samples_per_symbol)) + time_array=modulator_utils.generate_timearray( + self.fs,interp_sample_count) + interpolated_phase=modulator_utils.previous_resample_interpolate( + time_array, self.baud, phase_data) + # Smooth phases with Gaussian kernel after mapping to complex plane + interpolated_phase=np.exp(1j*interpolated_phase) + shaped_phase=signal.convolve(interpolated_phase,gaussian_window, + "same",method="fft") + + # Construct smoothed signal mask + # TODO: be smarter about only convolving the edges + amplitude_mask=np.asarray([0]+[self.amplitude]*len(datastream)+[0]) + interpolated_amplitude=modulator_utils.previous_resample_interpolate( + time_array,self.baud,amplitude_mask) + shaped_amplitude=signal.convolve(interpolated_amplitude,gaussian_window, + "same",method="fft") + + shaped_phase*=shaped_amplitude + + # Multiply amplitudes by carrier + return np.real(shaped_phase * + np.exp(2*np.pi*1j*self.carrier_freq*time_array)) + + def demodulate(self, modulated_data): + # TODO: complete + samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) + time_array=modulator_utils.generate_timearray( + self.fs,len(modulated_data)) + demod_phase=2*modulated_data*np.exp(2*np.pi*1j*self.carrier_freq*time_array) + + # Compute filter boundaries + # Lowend is half the baud (i.e. the fundamental of the data) + # Highend blocks fundamental of data and optionally voice + # TODO: improve this part + carrier_refl=min(2*self.carrier_freq,self.fs-2*self.carrier_freq) + filter_lowend=0.5*self.baud + filter_highend=carrier_refl-filter_lowend + if carrier_refl-4000>self.carrier_freq: + filter_highend=carrier_refl-4000 + + # Construct FIR filter, filter demodulated signal, and discard phase + fir_filt=modulator_utils.lowpass_fir_filter(self.fs, filter_lowend, filter_highend) + filt_delay=(len(fir_filt)-1)//2 + + filtered_demod_phase=np.angle(signal.lfilter(fir_filt,1,demod_phase)) + filtered_demod_phase[filtered_demod_phase<0]+=2*np.pi + + # Extract the original amplitudes via averaging of plateau + + # Round to account for floating point weirdness + interval_count=int(np.round( + len(modulated_data)/samples_per_symbol)) + interval_offset=filt_delay + list_phases=list() + + transition_width=BaseModulator.sigma_mult_t*self._calculate_sigma + # Convert above time width into sample point width + transition_width*=self.fs + + for i in range(interval_count): + interval_begin=interval_offset+i*samples_per_symbol + # Perform min in order to account for floating point weirdness + interval_end=min(interval_begin+samples_per_symbol, + len(modulated_data)-1) + + # Shrink interval by previously calculated transition width + # Skip doing so for first and last sample + if i!=0: + interval_begin+=transition_width + if i!=interval_count-1: + interval_end-=transition_width + # Find the amplitude by averaging + list_phases.append(modulator_utils.average_interval_data(filtered_demod_phase, interval_begin, interval_end)) + + list_phases=list_phases.copy() + # Convert amplitude observations and mapping into vq arguments + # Insert the null symbol 0 to account for beginning and end + list_phases=[[phase] for phase in list_phases] + code_book=[self.amp_list[i] for i in range(len(self.amp_list))] + code_book.insert(0,0.0) + code_book=[[obs] for obs in code_book] + + # Map averages to amplitude points + vector_cluster=vq(list_phases,code_book) + # Subtract data points by 1 and remove 0 padding + # Neat side effect: -1 is an invalid data point + datastream=vector_cluster[0]-1 + if (datastream[0]!=-1 or datastream[-1]!=-1 + or any(datastream[1:-1]==-1)): + warnings.warn("Corrupted datastream detected while demodulating", + ModulationIntegrityWarning) + return datastream[1:-1] diff --git a/voicechat_modem_dsp/modulators/modulator_psk.pyi b/voicechat_modem_dsp/modulators/modulator_psk.pyi new file mode 100644 index 0000000..9ca3dff --- /dev/null +++ b/voicechat_modem_dsp/modulators/modulator_psk.pyi @@ -0,0 +1,14 @@ +from .modulator_base import BaseModulator +from typing import Sequence, Dict, Tuple + +from numpy import ndarray + +class PSKModulator(BaseModulator): + fs: float = ... + phase_list: Dict[int, float] = ... + carrier_freq: float = ... + baud: float = ... + def __init__(self, fs: float, carrier: float, amplitude: float + phase_list: Sequence[float], baud: float) -> None: ... + def modulate(self, datastream: Sequence[int]) -> ndarray: ... + def demodulate(self, modulated_data: ndarray) -> Sequence[int]: ... From 3e606e00dde58cf93ef6e90b3cb027edfe45bcb3 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 1 Feb 2020 14:16:15 -0500 Subject: [PATCH 180/228] Fix missing comma for PSK typestub --- voicechat_modem_dsp/modulators/modulator_psk.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_psk.pyi b/voicechat_modem_dsp/modulators/modulator_psk.pyi index 9ca3dff..a5f1730 100644 --- a/voicechat_modem_dsp/modulators/modulator_psk.pyi +++ b/voicechat_modem_dsp/modulators/modulator_psk.pyi @@ -8,7 +8,7 @@ class PSKModulator(BaseModulator): phase_list: Dict[int, float] = ... carrier_freq: float = ... baud: float = ... - def __init__(self, fs: float, carrier: float, amplitude: float + def __init__(self, fs: float, carrier: float, amplitude: float, phase_list: Sequence[float], baud: float) -> None: ... def modulate(self, datastream: Sequence[int]) -> ndarray: ... def demodulate(self, modulated_data: ndarray) -> Sequence[int]: ... From 400bd05fd6cefc19ce440f3e70df46d5fc5e1d53 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 9 Feb 2020 10:07:04 -0500 Subject: [PATCH 181/228] Adjust a small comment in FSK modulator --- voicechat_modem_dsp/modulators/modulator_fsk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index adc3230..12ce749 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -67,7 +67,7 @@ def modulate(self, datastream): interpolated_frequency=modulator_utils.previous_resample_interpolate( time_array, self.baud, frequency_data) - # Construct smoothed frequency mask + # Construct smoothed signal mask # TODO: be smarter about only convolving the edges amplitude_mask=np.asarray([0]+[self.amplitude]*len(datastream)+[0]) interpolated_amplitude=modulator_utils.previous_resample_interpolate( From d7050d836b9c7eebf9284bffc66355fce8f4d026 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 9 Feb 2020 10:07:27 -0500 Subject: [PATCH 182/228] Complete the PSK Demodulator --- .../modulators/modulator_psk.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_psk.py b/voicechat_modem_dsp/modulators/modulator_psk.py index b21ba09..e9a3a4d 100644 --- a/voicechat_modem_dsp/modulators/modulator_psk.py +++ b/voicechat_modem_dsp/modulators/modulator_psk.py @@ -55,7 +55,6 @@ def _calculate_sigma(self): return min(gaussian_sigma_t,gaussian_sigma_f) def modulate(self, datastream): - # TODO: complete samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) gaussian_sigma=self._calculate_sigma gaussian_window=modulator_utils.gaussian_window(self.fs,gaussian_sigma) @@ -91,11 +90,12 @@ def modulate(self, datastream): np.exp(2*np.pi*1j*self.carrier_freq*time_array)) def demodulate(self, modulated_data): - # TODO: complete + # TODO: copy this over to the QAM modulator + # This is close enough to maybe allow object composition samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) time_array=modulator_utils.generate_timearray( self.fs,len(modulated_data)) - demod_phase=2*modulated_data*np.exp(2*np.pi*1j*self.carrier_freq*time_array) + demod_signal=2*modulated_data*np.exp(2*np.pi*1j*self.carrier_freq*time_array) # Compute filter boundaries # Lowend is half the baud (i.e. the fundamental of the data) @@ -111,8 +111,7 @@ def demodulate(self, modulated_data): fir_filt=modulator_utils.lowpass_fir_filter(self.fs, filter_lowend, filter_highend) filt_delay=(len(fir_filt)-1)//2 - filtered_demod_phase=np.angle(signal.lfilter(fir_filt,1,demod_phase)) - filtered_demod_phase[filtered_demod_phase<0]+=2*np.pi + filtered_demod_signal=signal.lfilter(fir_filt,1,demod_signal) # Extract the original amplitudes via averaging of plateau @@ -120,7 +119,7 @@ def demodulate(self, modulated_data): interval_count=int(np.round( len(modulated_data)/samples_per_symbol)) interval_offset=filt_delay - list_phases=list() + list_constellation=list() transition_width=BaseModulator.sigma_mult_t*self._calculate_sigma # Convert above time width into sample point width @@ -139,18 +138,19 @@ def demodulate(self, modulated_data): if i!=interval_count-1: interval_end-=transition_width # Find the amplitude by averaging - list_phases.append(modulator_utils.average_interval_data(filtered_demod_phase, interval_begin, interval_end)) + list_constellation.append(modulator_utils.average_interval_data(filtered_demod_signal, interval_begin, interval_end)) - list_phases=list_phases.copy() - # Convert amplitude observations and mapping into vq arguments + list_constellation=list_constellation.copy() + # Convert observations and mapping into vq arguments # Insert the null symbol 0 to account for beginning and end - list_phases=[[phase] for phase in list_phases] - code_book=[self.amp_list[i] for i in range(len(self.amp_list))] - code_book.insert(0,0.0) - code_book=[[obs] for obs in code_book] + list_constellation=[[np.real(point),-np.imag(point)] for point in list_constellation] + code_book=[self.amplitude*np.exp(1j*self.phase_list[i]) + for i in range(len(self.phase_list))] + code_book.insert(0,0+0j) + code_book=[[np.real(obs),np.imag(obs)] for obs in code_book] # Map averages to amplitude points - vector_cluster=vq(list_phases,code_book) + vector_cluster=vq(list_constellation,code_book) # Subtract data points by 1 and remove 0 padding # Neat side effect: -1 is an invalid data point datastream=vector_cluster[0]-1 From 45d3eda0b6d79d8feafcfb433d297c1216bfe93d Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 9 Feb 2020 10:29:31 -0500 Subject: [PATCH 183/228] Update tests to include integrity test for PSKModulator --- test/modulators/test_modulator_integrity.py | 36 +++++++++++++++++++-- voicechat_modem_dsp/modulators/__init__.py | 3 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/test/modulators/test_modulator_integrity.py b/test/modulators/test_modulator_integrity.py index 687b49a..5e0cb5d 100644 --- a/test/modulators/test_modulator_integrity.py +++ b/test/modulators/test_modulator_integrity.py @@ -4,8 +4,8 @@ from scipy.signal import hilbert from voicechat_modem_dsp.encoders.encode_pad import * -from voicechat_modem_dsp.modulators import ASKModulator, FSKModulator, \ - ModulationIntegrityWarning +from voicechat_modem_dsp.modulators import ModulationIntegrityWarning, \ + ASKModulator, FSKModulator, PSKModulator import pytest @@ -128,6 +128,38 @@ def test_property_ask_integrity(): assert bitstream==recovered_bitstream +@pytest.mark.property +def test_property_psk_integrity(): + phase_list=list(np.linspace(0,2*np.pi,16,endpoint=False)) + count_run=0 + while count_run<256: + # Shuffle as opposed to complete random to test all 0x00-0xff + list_data=list(range(256)) + random.shuffle(list_data) + bitstream=bytes(list_data) + datastream=base_16_encode(bitstream) + + sampling_freq=get_rand_float(8000,48000) + carrier_freq=get_rand_float(256,sampling_freq/2) + baud_rate=get_rand_float(128,carrier_freq/4) + amplitude=get_rand_float(0.1,1) + + with warnings.catch_warnings(): + warnings.simplefilter("error",category=ModulationIntegrityWarning) + try: + modulator=PSKModulator(sampling_freq, + carrier_freq,amplitude,phase_list,baud_rate) + except ModulationIntegrityWarning: + continue + + modulated_data=modulator.modulate(datastream) + demodulated_bundle=modulator.demodulate(modulated_data) + + recovered_bitstream=base_16_decode(demodulated_bundle) + count_run+=1 + + assert bitstream==recovered_bitstream + @pytest.mark.property def test_property_fsk_integrity(): frequency_list=list(np.linspace(512,2048,16)) diff --git a/voicechat_modem_dsp/modulators/__init__.py b/voicechat_modem_dsp/modulators/__init__.py index 0d5ee3a..b46d798 100644 --- a/voicechat_modem_dsp/modulators/__init__.py +++ b/voicechat_modem_dsp/modulators/__init__.py @@ -1,9 +1,10 @@ from .modulator_ask import ASKModulator from .modulator_fsk import FSKModulator +from .modulator_psk import PSKModulator from .modulator_base import BaseModulator from .modulator_utils import ModulationIntegrityWarning -_public_interface=[ASKModulator, FSKModulator, BaseModulator, +_public_interface=[ASKModulator, FSKModulator, PSKModulator, BaseModulator, ModulationIntegrityWarning] __all__=[type_obj.__name__ for type_obj in _public_interface] From ae6373115cc6e8777a77dd39f7f19238b4d2550e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 9 Feb 2020 10:30:12 -0500 Subject: [PATCH 184/228] Begin testing PSKModulator invalid parameters --- test/modulators/test_modulator_parameters.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/modulators/test_modulator_parameters.py b/test/modulators/test_modulator_parameters.py index 389aeb5..ceaf6c6 100644 --- a/test/modulators/test_modulator_parameters.py +++ b/test/modulators/test_modulator_parameters.py @@ -2,8 +2,8 @@ import numpy as np from voicechat_modem_dsp.encoders.encode_pad import * -from voicechat_modem_dsp.modulators import ASKModulator, FSKModulator, \ - ModulationIntegrityWarning +from voicechat_modem_dsp.modulators import ModulationIntegrityWarning, \ + ASKModulator, FSKModulator, PSKModulator import pytest @@ -14,6 +14,11 @@ def test_invalid_nyquist(): with pytest.raises(ValueError, match=r"Baud is too high.+"): bad_modulator=ASKModulator(1000,100,np.linspace(0.2,1,8),80) + with pytest.raises(ValueError, match=r"Carrier frequency is too high.+"): + bad_modulator=PSKModulator(1000,600,1,np.linspace(0,np.pi,8),20) + with pytest.raises(ValueError, match=r"Baud is too high.+"): + bad_modulator=PSKModulator(1000,100,1,np.linspace(0,np.pi,8),80) + with pytest.raises(ValueError, match=r"Baud is too high.+"): bad_modulator=FSKModulator(1000,1,np.linspace(200,1000,8),800) with pytest.raises(ValueError, match=r"Maximum frequency is too high.+"): @@ -21,6 +26,7 @@ def test_invalid_nyquist(): with pytest.warns(ModulationIntegrityWarning): bad_modulator=ASKModulator(900,400,np.linspace(0.2,1,8),100) + bad_modulator=PSKModulator(900,400,1,np.linspace(0,np.pi,8),100) @pytest.mark.unit def test_invalid_modspecific(): From 1ccfcaedc543f6bf8a9217ac3152caddeaff43e6 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 9 Feb 2020 12:01:46 -0500 Subject: [PATCH 185/228] Complete PSK parameter tests --- test/modulators/test_modulator_parameters.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/modulators/test_modulator_parameters.py b/test/modulators/test_modulator_parameters.py index ceaf6c6..6d1e01a 100644 --- a/test/modulators/test_modulator_parameters.py +++ b/test/modulators/test_modulator_parameters.py @@ -37,6 +37,17 @@ def test_invalid_modspecific(): with pytest.warns(ModulationIntegrityWarning): bad_modulator=ASKModulator(2000,880,np.geomspace(0.2,1,256),40) + with pytest.raises(ValueError, match=r"Invalid phases.+"): + bad_modulator=PSKModulator(1000,200,1,np.linspace(-1,10,16),20) + with pytest.raises(ValueError, match=r"amplitude must be positive.+"): + bad_modulator=PSKModulator(1000,200,-1,np.linspace(0,3*np.pi/2,16),20) + with pytest.warns(ModulationIntegrityWarning): + bad_modulator=PSKModulator(1000,200,1,np.linspace(0,0.1,16),50) + with pytest.warns(ModulationIntegrityWarning): + bad_modulator=PSKModulator(2000,880,1,np.linspace(0,3*np.pi/2,16),20) + with pytest.warns(ModulationIntegrityWarning): + bad_modulator=PSKModulator(1000,200,0.02,np.linspace(0,0.1,16),50) + with pytest.raises(ValueError, match=r"Invalid frequencies.+"): bad_modulator=FSKModulator(1000,1,np.linspace(-1,1000,16),500) with pytest.raises(ValueError, match=r"amplitude must be positive.+"): From c9ac1fea5f654bfa03f70561e50f3198d9756709 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 9 Feb 2020 16:03:57 -0500 Subject: [PATCH 186/228] Update Pipfile.lock --- Pipfile.lock | 306 ++++++++++++++++++++++++++------------------------- 1 file changed, 156 insertions(+), 150 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index e26d93e..12ed16c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -52,18 +52,18 @@ }, "ipykernel": { "hashes": [ - "sha256:1a7def9c986f1ee018c1138d16951932d4c9d4da01dad45f9d34e9899565a22f", - "sha256:b368ad13edb71fa2db367a01e755a925d7f75ed5e09fbd3f06c85e7a8ef108a8" + "sha256:7f1f01df22f1229c8879501057877ccaf92a3b01c1d00db708aad5003e5f9238", + "sha256:ba8c9e5561f3223fb47ce06ad7925cb9444337ac367341c0c520ffb68ea6d120" ], "index": "pypi", - "version": "==5.1.3" + "version": "==5.1.4" }, "ipython": { "hashes": [ - "sha256:0f4bcf18293fb666df8511feec0403bdb7e061a5842ea6e88a3177b0ceb34ead", - "sha256:387686dd7fc9caf29d2fddcf3116c4b07a11d9025701d220c589a430b0171d8a" + "sha256:d9459e7237e2e5858738ff9c3e26504b79899b58a6d49e574d352493d80684c6", + "sha256:f6689108b1734501d3b59c84427259fd5ac5141afe2e846cfa8598eb811886c9" ], - "version": "==7.11.1" + "version": "==7.12.0" }, "ipython-genutils": { "hashes": [ @@ -74,10 +74,10 @@ }, "jedi": { "hashes": [ - "sha256:1349c1e8c107095a55386628bb3b2a79422f3a2cab8381e34ce19909e0cf5064", - "sha256:e909527104a903606dd63bea6e8e888833f0ef087057829b89a18364a856f807" + "sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2", + "sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5" ], - "version": "==0.15.2" + "version": "==0.16.0" }, "jupyter-client": { "hashes": [ @@ -137,22 +137,23 @@ }, "matplotlib": { "hashes": [ - "sha256:08ccc8922eb4792b91c652d3e6d46b1c99073f1284d1b6705155643e8046463a", - "sha256:161dcd807c0c3232f4dcd4a12a382d52004a498174cbfafd40646106c5bcdcc8", - "sha256:1f9e885bfa1b148d16f82a6672d043ecf11197f6c71ae222d0546db706e52eb2", - "sha256:2d6ab54015a7c0d727c33e36f85f5c5e4172059efdd067f7527f6e5d16ad01aa", - "sha256:5d2e408a2813abf664bd79431107543ecb449136912eb55bb312317edecf597e", - "sha256:61c8b740a008218eb604de518eb411c4953db0cb725dd0b32adf8a81771cab9e", - "sha256:80f10af8378fccc136da40ea6aa4a920767476cdfb3241acb93ef4f0465dbf57", - "sha256:819d4860315468b482f38f1afe45a5437f60f03eaede495d5ff89f2eeac89500", - "sha256:8cc0e44905c2c8fda5637cad6f311eb9517017515a034247ab93d0cf99f8bb7a", - "sha256:8e8e2c2fe3d873108735c6ee9884e6f36f467df4a143136209cff303b183bada", - "sha256:98c2ffeab8b79a4e3a0af5dd9939f92980eb6e3fec10f7f313df5f35a84dacab", - "sha256:d59bb0e82002ac49f4152963f8a1079e66794a4f454457fd2f0dcc7bf0797d30", - "sha256:ee59b7bb9eb75932fe3787e54e61c99b628155b0cedc907864f24723ba55b309" + "sha256:23b71560c721109954c0215ffc81f4c80ce8528749d534a01a61e8ab737c5bce", + "sha256:4164265ca573481ce61c83322e6b33628203afeabeb3e22c50376f5d3ee0f9be", + "sha256:470eed601ff5132364e0121a20d7c3d43fab969c8c333422c1b6b72fde2ed3c1", + "sha256:635ded7834f43c8d999076236f7e90074d77f7b8345e5e82cd95af053cc29df1", + "sha256:6a0031774c6c68298183438edf2e738856d63a4c4797876fa81d0ee337f5361c", + "sha256:78d0772412c0653aa3e860c52ff08d1f5ba64334e2b86b09dc2d502657d8ca73", + "sha256:8efff896c49676700dc6adace6137a854ff64a4d44ca057ff726960ffdaa47bf", + "sha256:97f04d29a358826f205320fbc88d46ce5c5ff6fb54ae050042ff396beda52ca4", + "sha256:b4c0010eff09ab65c77ad1a0eec6c7cccb9f6838c3c77dc5b4002fe0cf2912fd", + "sha256:b5ace0531255932ad19fe64c116ada2713f7b38381db8f68df0fa694409e67d1", + "sha256:c7bb7ed3e011324b56462391ec3f4bbb7c8c6af5892ebfb45d312b15b4cdfc8d", + "sha256:db3121f12fb9b99f105d1413aebaeb3d943f269f3d262b45586d12765866f0c6", + "sha256:db8bbba9284845034a2f0e1add91dc5e89db8c996359bdcf677a8d6f88875cf1", + "sha256:f0023322c99328c40ce22678ab0ab5adfc27e338419966539398239996f63e8d" ], "index": "pypi", - "version": "==3.1.2" + "version": "==3.1.3" }, "numpy": { "hashes": [ @@ -183,10 +184,10 @@ }, "parso": { "hashes": [ - "sha256:55cf25df1a35fd88b878715874d2c4dc1ad3f0eebd1e0266a67e1f55efccfbe1", - "sha256:5c1f7791de6bd5dbbeac8db0ef5594b36799de198b3f7f7014643b0c5536b9d3" + "sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57", + "sha256:951af01f61e6dccd04159042a0706a31ad437864ec6e25d0d7a96a9fbb9b0095" ], - "version": "==0.5.2" + "version": "==0.6.1" }, "pastel": { "hashes": [ @@ -197,11 +198,11 @@ }, "pexpect": { "hashes": [ - "sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", - "sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb" + "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", + "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" ], "markers": "sys_platform != 'win32'", - "version": "==4.7.0" + "version": "==4.8.0" }, "pickleshare": { "hashes": [ @@ -212,35 +213,35 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:0278d2f51b5ceba6ea8da39f76d15684e84c996b325475f6e5720edc584326a7", - "sha256:63daee79aa8366c8f1c637f1a4876b890da5fc92a19ebd2f7080ebacb901e990" + "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e", + "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a" ], - "version": "==3.0.2" + "version": "==3.0.3" }, "protobuf": { "hashes": [ - "sha256:0329e86a397db2a83f9dcbe21d9be55a47f963cdabc893c3a24f4d3a8f117c37", - "sha256:0a7219254afec0d488211f3d482d8ed57e80ae735394e584a98d8f30a8c88a36", - "sha256:14d6ac53df9cb5bb87c4f91b677c1bc5cec9c0fd44327f367a3c9562de2877c4", - "sha256:180fc364b42907a1d2afa183ccbeffafe659378c236b1ec3daca524950bb918d", - "sha256:3d7a7d8d20b4e7a8f63f62de2d192cfd8b7a53c56caba7ece95367ca2b80c574", - "sha256:3f509f7e50d806a434fe4a5fbf602516002a0f092889209fff7db82060efffc0", - "sha256:4571da974019849201fc1ec6626b9cea54bd11b6bed140f8f737c0a33ea37de5", - "sha256:557686c43fbd04f5f7c533f00feee9a37dcca7b5896e3ae3664a33864e6dd546", - "sha256:56bd1d84fbf4505c7b73f04de987eef5682e5752c811141b0186a3809bfb396f", - "sha256:680c668d00b5eff08b86aef9e5ba9a705e621ea05d39071cfea8e28cb2400946", - "sha256:6b5b947dc8b3f2aec0eaad65b0b5113fcd642c358c31357c647da6281ee31104", - "sha256:6e96dffaf4d0a9a329e528b353ba62fd9ef13599688723d96bc9c165d0b6871e", - "sha256:919f0d6f6addc836d08658eba3b52be2e92fd3e76da3ce00c325d8e9826d17c7", - "sha256:9c7b19c30cf0644afd0e4218b13f637ce54382fdcb1c8f75bf3e84e49a5f6d0a", - "sha256:a2e6f57114933882ec701807f217df2fb4588d47f71f227c0a163446b930d507", - "sha256:a6b970a2eccfcbabe1acf230fbf112face1c4700036c95e195f3554d7bcb04c1", - "sha256:bc45641cbcdea068b67438244c926f9fd3e5cbdd824448a4a64370610df7c593", - "sha256:d61b14a9090da77fe87e38ba4c6c43d3533dcbeb5d84f5474e7ac63c532dcc9c", - "sha256:d6faf5dbefb593e127463f58076b62fcfe0784187be8fe1aa9167388f24a22a1" + "sha256:0bae429443cc4748be2aadfdaf9633297cfaeb24a9a02d0ab15849175ce90fab", + "sha256:24e3b6ad259544d717902777b33966a1a069208c885576254c112663e6a5bb0f", + "sha256:2affcaba328c4662f3bc3c0e9576ea107906b2c2b6422344cdad961734ff6b93", + "sha256:310a7aca6e7f257510d0c750364774034272538d51796ca31d42c3925d12a52a", + "sha256:52e586072612c1eec18e1174f8e3bb19d08f075fc2e3f91d3b16c919078469d0", + "sha256:73152776dc75f335c476d11d52ec6f0f6925774802cd48d6189f4d5d7fe753f4", + "sha256:7774bbbaac81d3ba86de646c39f154afc8156717972bf0450c9dbfa1dc8dbea2", + "sha256:82d7ac987715d8d1eb4068bf997f3053468e0ce0287e2729c30601feb6602fee", + "sha256:8eb9c93798b904f141d9de36a0ba9f9b73cc382869e67c9e642c0aba53b0fc07", + "sha256:adf0e4d57b33881d0c63bb11e7f9038f98ee0c3e334c221f0858f826e8fb0151", + "sha256:c40973a0aee65422d8cb4e7d7cbded95dfeee0199caab54d5ab25b63bce8135a", + "sha256:c77c974d1dadf246d789f6dad1c24426137c9091e930dbf50e0a29c1fcf00b1f", + "sha256:dd9aa4401c36785ea1b6fff0552c674bdd1b641319cb07ed1fe2392388e9b0d7", + "sha256:e11df1ac6905e81b815ab6fd518e79be0a58b5dc427a2cf7208980f30694b956", + "sha256:e2f8a75261c26b2f5f3442b0525d50fd79a71aeca04b5ec270fc123536188306", + "sha256:e512b7f3a4dd780f59f1bf22c302740e27b10b5c97e858a6061772668cd6f961", + "sha256:ef2c2e56aaf9ee914d3dccc3408d42661aaf7d9bb78eaa8f17b2e6282f214481", + "sha256:fac513a9dc2a74b99abd2e17109b53945e364649ca03d9f7a0b96aa8d1807d0a", + "sha256:fdfb6ad138dbbf92b5dbea3576d7c8ba7463173f7d2cb0ca1bd336ec88ddbd80" ], "index": "pypi", - "version": "==3.11.2" + "version": "==3.11.3" }, "ptyprocess": { "hashes": [ @@ -312,10 +313,10 @@ }, "ruamel.yaml": { "hashes": [ - "sha256:0db639b1b2742dae666c6fc009b8d1931ef15c9276ef31c0673cc6dcf766cf40", - "sha256:412a6f5cfdc0525dee6a27c08f5415c7fd832a7afcb7a0ed7319628aed23d408" + "sha256:9d59fa89985c55155d35c886663e357813404ae8f94638cb673135b8c8c1a7c7", + "sha256:dba517a7e330b6caf476b757022d21efa13c32694bfba1e057ce59a374f18f0a" ], - "version": "==0.16.5" + "version": "==0.16.7" }, "ruamel.yaml.clib": { "hashes": [ @@ -371,10 +372,10 @@ }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "strictyaml": { "hashes": [ @@ -455,40 +456,40 @@ }, "coverage": { "hashes": [ - "sha256:189aac76d6e0d7af15572c51892e7326ee451c076c5a50a9d266406cd6c49708", - "sha256:1bf7ba2af1d373a1750888724f84cffdfc697738f29a353c98195f98fc011509", - "sha256:1f4ee8e2e4243971618bc16fcc4478317405205f135e95226c2496e2a3b8dbbf", - "sha256:225e79a5d485bc1642cb7ba02281419c633c216cdc6b26c26494ba959f09e69f", - "sha256:23688ff75adfa8bfa2a67254d889f9bdf9302c27241d746e17547c42c732d3f4", - "sha256:28f7f73b34a05e23758e860a89a7f649b85c6749e252eff60ebb05532d180e86", - "sha256:2d0cb9b1fe6ad0d915d45ad3d87f03a38e979093a98597e755930db1f897afae", - "sha256:47874b4711c5aeb295c31b228a758ce3d096be83dc37bd56da48ed99efb8813b", - "sha256:511ec0c00840e12fb4e852e4db58fa6a01ca4da72f36a9766fae344c3d502033", - "sha256:53e7438fef0c97bc248f88ba1edd10268cd94d5609970aaf87abbe493691af87", - "sha256:569f9ee3025682afda6e9b0f5bb14897c0db03f1a1dc088b083dd36e743f92bb", - "sha256:593853aa1ac6dcc6405324d877544c596c9d948ef20d2e9512a0f5d2d3202356", - "sha256:5b0a07158360d22492f9abd02a0f2ee7981b33f0646bf796598b7673f6bbab14", - "sha256:7ca3db38a61f3655a2613ee2c190d63639215a7a736d3c64cc7bbdb002ce6310", - "sha256:7d1cc7acc9ce55179616cf72154f9e648136ea55987edf84addbcd9886ffeba2", - "sha256:88b51153657612aea68fa684a5b88037597925260392b7bb4509d4f9b0bdd889", - "sha256:955ec084f549128fa2702f0b2dc696392001d986b71acd8fd47424f28289a9c3", - "sha256:b251c7092cbb6d789d62dc9c9e7c4fb448c9138b51285c36aeb72462cad3600e", - "sha256:bd82b684bb498c60ef47bb1541a50e6d006dde8579934dcbdbc61d67d1ea70d9", - "sha256:bfe102659e2ec13b86c7f3b1db6c9a4e7beea4255058d006351339e6b342d5d2", - "sha256:c1e4e39e43057396a5e9d069bfbb6ffeee892e40c5d2effbd8cd71f34ee66c4d", - "sha256:cb2b74c123f65e8166f7e1265829a6c8ed755c3cd16d7f50e75a83456a5f3fd7", - "sha256:cca38ded59105f7705ef6ffe1e960b8db6c7d8279c1e71654a4775ab4454ca15", - "sha256:cf908840896f7aa62d0ec693beb53264b154f972eb8226fb864ac38975590c4f", - "sha256:d095a7b473f8a95f7efe821f92058c8a2ecfb18f8db6677ae3819e15dc11aaae", - "sha256:d22b4297e7e4225ccf01f1aa55e7a96412ea0796b532dd614c3fcbafa341128e", - "sha256:d4a2b578a7a70e0c71f662705262f87a456f1e6c1e40ada7ea699abaf070a76d", - "sha256:ddeb42a3d5419434742bf4cc71c9eaa22df3b76808e23a82bd0b0bd360f1a9f1", - "sha256:e65a5aa1670db6263f19fdc03daee1d7dbbadb5cb67fd0a1f16033659db13c1d", - "sha256:eaad65bd20955131bcdb3967a4dea66b4e4d4ca488efed7c00d91ee0173387e8", - "sha256:f45fba420b94165c17896861bb0e8b27fb7abdcedfeb154895d8553df90b7b00" + "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3", + "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c", + "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0", + "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477", + "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a", + "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf", + "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691", + "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73", + "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987", + "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894", + "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e", + "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef", + "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf", + "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68", + "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8", + "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954", + "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2", + "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40", + "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc", + "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc", + "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e", + "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d", + "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f", + "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc", + "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301", + "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea", + "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb", + "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af", + "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52", + "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37", + "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0" ], "index": "pypi", - "version": "==5.0.2" + "version": "==5.0.3" }, "decorator": { "hashes": [ @@ -506,11 +507,10 @@ }, "docutils": { "hashes": [ - "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", - "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", - "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" + "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", + "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" ], - "version": "==0.15.2" + "version": "==0.16" }, "entrypoints": { "hashes": [ @@ -535,11 +535,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45", - "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f" + "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", + "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" ], "markers": "python_version < '3.8'", - "version": "==1.3.0" + "version": "==1.5.0" }, "ipython-genutils": { "hashes": [ @@ -558,10 +558,10 @@ }, "jinja2": { "hashes": [ - "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", - "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" + "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", + "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" ], - "version": "==2.10.3" + "version": "==2.11.1" }, "jsonschema": { "hashes": [ @@ -583,13 +583,16 @@ "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", @@ -606,7 +609,9 @@ "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" ], "version": "==1.1.1" }, @@ -619,10 +624,10 @@ }, "more-itertools": { "hashes": [ - "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", - "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564" + "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", + "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" ], - "version": "==8.0.2" + "version": "==8.2.0" }, "mypy": { "hashes": [ @@ -661,18 +666,18 @@ }, "nbformat": { "hashes": [ - "sha256:cca9a1acfd4e049dcd6c3628d3c84db8e48a770182fb7b87d6a62f9ceacfae39", - "sha256:d1407544cf0c53ee88f504b6c732aef6e0f407a0858b405fcf133e0a25bb787b" + "sha256:562de41fc7f4f481b79ab5d683279bf3a168858268d4387b489b7b02be0b324a", + "sha256:f4bbbd8089bd346488f00af4ce2efb7f8310a74b2058040d075895429924678c" ], - "version": "==5.0.3" + "version": "==5.0.4" }, "nbsphinx": { "hashes": [ - "sha256:16fad6f2a7191972c98df84e76b78f86161f859682e44107668384f916aee00d", - "sha256:2d28bde42b22b66ea8699aa8275547baa524c296ee18d440448b7ab000f96f65" + "sha256:1c3e1b5c5b4c96c06c2e428536b6a7860ee23bc290dc4e6e73ee6ed5076a004e", + "sha256:e100472db293dcc2f7aae04ebebec0bfbe8eba88e209d04f367373fdd4b1455d" ], "index": "pypi", - "version": "==0.5.0" + "version": "==0.5.1" }, "numpy": { "hashes": [ @@ -707,10 +712,10 @@ }, "packaging": { "hashes": [ - "sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb", - "sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8" + "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", + "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334" ], - "version": "==20.0" + "version": "==20.1" }, "pandocfilters": { "hashes": [ @@ -754,11 +759,11 @@ }, "pytest": { "hashes": [ - "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa", - "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4" + "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d", + "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6" ], "index": "pypi", - "version": "==5.3.2" + "version": "==5.3.5" }, "pytz": { "hashes": [ @@ -776,10 +781,10 @@ }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "snowballstemmer": { "hashes": [ @@ -790,11 +795,11 @@ }, "sphinx": { "hashes": [ - "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159", - "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5" + "sha256:b3feeed2da588adabd0db5329a309f27317e98382122721511e901833d092028", + "sha256:fb2ce74c28154872757925edda0060b4c4102cdc86b8b30063f23399678de2ca" ], "index": "pypi", - "version": "==2.3.1" + "version": "==2.4.0" }, "sphinxcontrib-applehelp": { "hashes": [ @@ -854,28 +859,29 @@ }, "typed-ast": { "hashes": [ - "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", - "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", - "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", - "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", - "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", - "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", - "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", - "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", - "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", - "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", - "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", - "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", - "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", - "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", - "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", - "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", - "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", - "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", - "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", - "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" - ], - "version": "==1.4.0" + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + ], + "version": "==1.4.1" }, "typing-extensions": { "hashes": [ @@ -887,10 +893,10 @@ }, "urllib3": { "hashes": [ - "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", - "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" + "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", + "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" ], - "version": "==1.25.7" + "version": "==1.25.8" }, "wcwidth": { "hashes": [ @@ -908,10 +914,10 @@ }, "zipp": { "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + "sha256:5c56e330306215cd3553342cfafc73dda2c60792384117893f3a83f8a1209f50", + "sha256:d65287feb793213ffe11c0f31b81602be31448f38aeb8ffc2eb286c4f6f6657e" ], - "version": "==0.6.0" + "version": "==2.2.0" } } } From f415b7df022db96edf9ea904fb4d887ffd81b9d3 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 10 Feb 2020 22:52:24 -0500 Subject: [PATCH 187/228] Add PSK examples to docs and update config parser accordingly --- docs/specs/config.rst | 12 +++++++++++- docs/specs/examples/psk31.yaml | 8 ++++++++ test/cli/test_yaml_loader.py | 7 ++++--- voicechat_modem_dsp/cli/config_loader.py | 9 +++++++-- 4 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 docs/specs/examples/psk31.yaml diff --git a/docs/specs/config.rst b/docs/specs/config.rst index 0b1128e..a280671 100644 --- a/docs/specs/config.rst +++ b/docs/specs/config.rst @@ -29,10 +29,10 @@ Supported modulation modes are - Amplitude Shift Keying (``ask``) - Frequency Shift Keying (``fsk``) +- Phase Shift Keying (``psk``) Modes that will be supported in the future are -- Phase Shift Keying (``psk``) - Quadrature Amplitude Modulation (``qam``) Amplitude Shift Keying @@ -89,4 +89,14 @@ Example Files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: examples/ask_1k.yaml + :language: yaml + +31.25 baud PSK +~~~~~~~~~~~~~~ + +This modulation is similar to the amateur radio PSK31 mode. However, it uses +a more rudimentary Hamming 7,4 ECC and Gaussian pulse shaping, as opposed to +a raised cosine filter. + +..literalinclude:: examples/psk31.yaml :language: yaml \ No newline at end of file diff --git a/docs/specs/examples/psk31.yaml b/docs/specs/examples/psk31.yaml new file mode 100644 index 0000000..d865e83 --- /dev/null +++ b/docs/specs/examples/psk31.yaml @@ -0,0 +1,8 @@ +version: 0.1 +fs: 48000 +ecc: hamming_7_4 +modulators: + - mode: psk + baud: 31.25 + carrier: 1000 + phases: 0, 0.25, 0.5, 0.75 \ No newline at end of file diff --git a/test/cli/test_yaml_loader.py b/test/cli/test_yaml_loader.py index 5b28fb1..edce655 100644 --- a/test/cli/test_yaml_loader.py +++ b/test/cli/test_yaml_loader.py @@ -1,7 +1,8 @@ from voicechat_modem_dsp.cli.config_loader import parse_config_str, \ construct_modulators from voicechat_modem_dsp.cli.command_utils import CLIError, ExtendedCommand -from voicechat_modem_dsp.modulators import ASKModulator, FSKModulator +from voicechat_modem_dsp.modulators import ASKModulator, PSKModulator, \ + FSKModulator from .testing_utils import DirectoryChanger, FileCleanup import glob @@ -27,7 +28,7 @@ def test_load_doc_examples(): elif "fsk" in yaml_file: assert isinstance(modulator_list[0],FSKModulator) elif "psk" in yaml_file: - pass + assert isinstance(modulator_list[0],PSKModulator) elif "qam" in yaml_file: pass @@ -47,7 +48,7 @@ def test_load_doc_examples_staticmethod(): elif "fsk" in yaml_file: assert isinstance(modulator_list[0],FSKModulator) elif "psk" in yaml_file: - pass + assert isinstance(modulator_list[0],PSKModulator) elif "qam" in yaml_file: pass diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index 3382253..5fcbb80 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -3,11 +3,13 @@ from strictyaml import load, YAML, YAMLValidationError from .yaml_schema_validators import Complex -from ..modulators import ASKModulator, FSKModulator, BaseModulator +from ..modulators import ASKModulator, PSKModulator, FSKModulator, BaseModulator from typing import List as TypingList from typing import Mapping as TypingMap +from numpy import pi + init_config_schema=Map({ "version": Str(), "fs": Float(), @@ -75,7 +77,10 @@ def construct_modulators(config_dict: TypingMap) -> TypingList[BaseModulator]: amplitudes=modulator_config["amplitudes"] modulator_list.append(ASKModulator(fs, carrier, amplitudes, baud)) elif mode=="psk": - pass + carrier=modulator_config["carrier"] + phases=modulator_config["phases"] + phases=[2*pi*phase for phase in phases] + modulator_list.append(PSKModulator(fs, carrier, 1, phases, baud)) elif mode=="qam": pass elif mode=="fsk": From d88f820c613460aa2b0ebbd63375f4eff8e1b0e7 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 10 Feb 2020 22:58:39 -0500 Subject: [PATCH 188/228] Changes left behind when adding PSK to CLI --- docs/specs/config.rst | 2 +- voicechat_modem_dsp/cli/command_objects.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/specs/config.rst b/docs/specs/config.rst index a280671..a66b33b 100644 --- a/docs/specs/config.rst +++ b/docs/specs/config.rst @@ -98,5 +98,5 @@ This modulation is similar to the amateur radio PSK31 mode. However, it uses a more rudimentary Hamming 7,4 ECC and Gaussian pulse shaping, as opposed to a raised cosine filter. -..literalinclude:: examples/psk31.yaml +.. literalinclude:: examples/psk31.yaml :language: yaml \ No newline at end of file diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py index de77f14..dcd7931 100644 --- a/voicechat_modem_dsp/cli/command_objects.py +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -7,7 +7,7 @@ from .config_loader import construct_modulators from .command_utils import CLIError, ExtendedCommand -from ..modulators import ASKModulator, FSKModulator +from ..modulators import ASKModulator, PSKModulator, FSKModulator from ..encoders import encode_function_mappings, decode_function_mappings from ..encoders.ecc import hamming_7_4 @@ -84,6 +84,8 @@ def handle(self): constellation_length=len(modulator_obj.amp_list) elif isinstance(modulator_obj,FSKModulator): constellation_length=len(modulator_obj.freq_list) + elif isinstance(modulator_obj,PSKModulator): + constellation_length=len(modulator_obj.phase_list) else: raise CLIError("Modulator type is not yet supported.") @@ -158,6 +160,8 @@ def handle(self): constellation_length=len(modulator_obj.amp_list) elif isinstance(modulator_obj,FSKModulator): constellation_length=len(modulator_obj.freq_list) + elif isinstance(modulator_obj,PSKModulator): + constellation_length=len(modulator_obj.phase_list) else: raise CLIError("Modulator type is not yet supported.") From 1b139446cee84558b4e7b4d69e6f24ffae374ab6 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 29 Feb 2020 11:23:06 -0500 Subject: [PATCH 189/228] Slight refactorings of ASK and PSK Modulator --- voicechat_modem_dsp/modulators/modulator_ask.py | 1 - voicechat_modem_dsp/modulators/modulator_psk.py | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 8bdddb9..3185db2 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -125,7 +125,6 @@ def demodulate(self, modulated_data): # Find the amplitude by averaging list_amplitudes.append(modulator_utils.average_interval_data(filtered_demod_amplitude, interval_begin, interval_end)) - list_amplitudes_copy=list_amplitudes.copy() # Convert amplitude observations and mapping into vq arguments # Insert the null symbol 0 to account for beginning and end list_amplitudes=[[amplitude] for amplitude in list_amplitudes] diff --git a/voicechat_modem_dsp/modulators/modulator_psk.py b/voicechat_modem_dsp/modulators/modulator_psk.py index e9a3a4d..c6b4d8c 100644 --- a/voicechat_modem_dsp/modulators/modulator_psk.py +++ b/voicechat_modem_dsp/modulators/modulator_psk.py @@ -138,12 +138,13 @@ def demodulate(self, modulated_data): if i!=interval_count-1: interval_end-=transition_width # Find the amplitude by averaging - list_constellation.append(modulator_utils.average_interval_data(filtered_demod_signal, interval_begin, interval_end)) + avg=modulator_utils.average_interval_data(filtered_demod_signal, + interval_begin, interval_end) + list_constellation.append(np.conj(avg)) - list_constellation=list_constellation.copy() # Convert observations and mapping into vq arguments # Insert the null symbol 0 to account for beginning and end - list_constellation=[[np.real(point),-np.imag(point)] for point in list_constellation] + list_constellation=[[np.real(point),np.imag(point)] for point in list_constellation] code_book=[self.amplitude*np.exp(1j*self.phase_list[i]) for i in range(len(self.phase_list))] code_book.insert(0,0+0j) From 5900a1b08c1d0fd0b19f78a76eb82ac95e29e138 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 29 Feb 2020 11:24:06 -0500 Subject: [PATCH 190/228] Implement QAMModulator (mostly copying from SK) --- voicechat_modem_dsp/modulators/__init__.py | 5 +- .../modulators/modulator_qam.py | 158 ++++++++++++++++++ .../modulators/modulator_qam.pyi | 14 ++ 3 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 voicechat_modem_dsp/modulators/modulator_qam.py create mode 100644 voicechat_modem_dsp/modulators/modulator_qam.pyi diff --git a/voicechat_modem_dsp/modulators/__init__.py b/voicechat_modem_dsp/modulators/__init__.py index b46d798..5f02009 100644 --- a/voicechat_modem_dsp/modulators/__init__.py +++ b/voicechat_modem_dsp/modulators/__init__.py @@ -1,10 +1,11 @@ from .modulator_ask import ASKModulator from .modulator_fsk import FSKModulator from .modulator_psk import PSKModulator +from .modulator_qam import QAMModulator from .modulator_base import BaseModulator from .modulator_utils import ModulationIntegrityWarning -_public_interface=[ASKModulator, FSKModulator, PSKModulator, BaseModulator, - ModulationIntegrityWarning] +_public_interface=[ASKModulator, FSKModulator, PSKModulator, QAMModulator, + BaseModulator, ModulationIntegrityWarning] __all__=[type_obj.__name__ for type_obj in _public_interface] diff --git a/voicechat_modem_dsp/modulators/modulator_qam.py b/voicechat_modem_dsp/modulators/modulator_qam.py new file mode 100644 index 0000000..fe1a04b --- /dev/null +++ b/voicechat_modem_dsp/modulators/modulator_qam.py @@ -0,0 +1,158 @@ +from .modulator_base import BaseModulator + +from . import modulator_utils +from .modulator_utils import ModulationIntegrityWarning + +import numpy as np +from scipy import signal +from scipy.cluster.vq import vq + +import warnings + +class QAMModulator(BaseModulator): + def __init__(self, fs, carrier, constellation_list, baud): + if baud>=0.5*carrier: + raise ValueError("Baud is too high to be modulated "+ + "using carrier frequency") + # Nyquist limit + if carrier>=0.5*fs: + raise ValueError("Carrier frequency is too high for sampling rate") + + self.fs=fs + self.constellation_list=dict(enumerate(constellation_list)) + self.carrier_freq=carrier + self.baud=baud + + constellation_magnitudes=np.abs(constellation_list) + if any([amp>1 or amp<=0 for amp in constellation_magnitudes]): + raise ValueError("Amplitude of constellation points must be positive and at most 1") + if any([amp<0.1 for amp in constellation_magnitudes]): + warnings.warn("Some amplitudes may be too low " + "to be distinguishable from background noise", + ModulationIntegrityWarning) + + constellation_distances=list() + for i in range(len(constellation_list)-1): + for j in range(i+1,len(constellation_list)): + distance=abs(constellation_list[i]-constellation_list[j]) + constellation_distances.append(distance) + + # Nyquist aliased 2*carrier=carrier + if carrier>=(1/3)*fs: + warnings.warn("Carrier frequency is too high to guarantee " + "proper lowpass reconstruction", + ModulationIntegrityWarning) + + if min(constellation_distances)<=0.05: + warnings.warn("Constellation points may be too close " + "to be distinguishable from each other", + ModulationIntegrityWarning) + # TODO: additional warnings relating to filter overshoot and the like + + @property + def _calculate_sigma(self): + #sigma_t = w/4k, at most half of the pulse is smoothed away + gaussian_sigma_t=(1/self.baud)/(4*BaseModulator.sigma_mult_t) + # Ensure dropoff at halfway to doubled carrier frequency is -80dB + # This is not the same as carrier_freq because Nyquist reflections + doubled_carrier_refl=min( + 2*self.carrier_freq,self.fs-2*self.carrier_freq) + halfway_thresh=0.5*doubled_carrier_refl + gaussian_sigma_f=BaseModulator.sigma_mult_f/(2*np.pi*halfway_thresh) + return min(gaussian_sigma_t,gaussian_sigma_f) + + def modulate(self, datastream): + samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) + gaussian_sigma=self._calculate_sigma + gaussian_window=modulator_utils.gaussian_window(self.fs,gaussian_sigma) + + # Map datastream to phases and pad with 0 on both ends + constellation_data = np.pad([self.constellation_list[datum] for datum in datastream], + 1,mode="constant",constant_values=0+0j) + + # Upsample amplitude to actual sampling rate + interp_sample_count=int(np.ceil( + len(constellation_data)*samples_per_symbol)) + time_array=modulator_utils.generate_timearray( + self.fs,interp_sample_count) + interpolated_quad=modulator_utils.previous_resample_interpolate( + time_array, self.baud, constellation_data) + # Smooth phases with Gaussian kernel after mapping to complex plane + shaped_quad=signal.convolve(interpolated_quad,gaussian_window, + "same",method="fft") + + # Multiply amplitudes by carrier + return np.real(shaped_quad * + np.exp(2*np.pi*1j*self.carrier_freq*time_array)) + + def demodulate(self, modulated_data): + # TODO: copy this over to the QAM modulator + # This is close enough to maybe allow object composition + samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) + time_array=modulator_utils.generate_timearray( + self.fs,len(modulated_data)) + demod_signal=2*modulated_data*np.exp(2*np.pi*1j*self.carrier_freq*time_array) + + # Compute filter boundaries + # Lowend is half the baud (i.e. the fundamental of the data) + # Highend blocks fundamental of data and optionally voice + # TODO: improve this part + carrier_refl=min(2*self.carrier_freq,self.fs-2*self.carrier_freq) + filter_lowend=0.5*self.baud + filter_highend=carrier_refl-filter_lowend + if carrier_refl-4000>self.carrier_freq: + filter_highend=carrier_refl-4000 + + # Construct FIR filter, filter demodulated signal, and discard phase + fir_filt=modulator_utils.lowpass_fir_filter(self.fs, filter_lowend, filter_highend) + filt_delay=(len(fir_filt)-1)//2 + + filtered_demod_signal=signal.lfilter(fir_filt,1,demod_signal) + + # Extract the original amplitudes via averaging of plateau + + # Round to account for floating point weirdness + interval_count=int(np.round( + len(modulated_data)/samples_per_symbol)) + interval_offset=filt_delay + list_constellation=list() + + transition_width=BaseModulator.sigma_mult_t*self._calculate_sigma + # Convert above time width into sample point width + transition_width*=self.fs + + for i in range(interval_count): + interval_begin=interval_offset+i*samples_per_symbol + # Perform min in order to account for floating point weirdness + interval_end=min(interval_begin+samples_per_symbol, + len(modulated_data)-1) + + # Shrink interval by previously calculated transition width + # Skip doing so for first and last sample + if i!=0: + interval_begin+=transition_width + if i!=interval_count-1: + interval_end-=transition_width + # Find the amplitude by averaging + avg=modulator_utils.average_interval_data(filtered_demod_signal, + interval_begin, interval_end) + list_constellation.append(np.conj(avg)) + + # Convert observations and mapping into vq arguments + # Insert the null symbol 0 to account for beginning and end + list_constellation=[[np.real(point),np.imag(point)] for point in list_constellation] + code_book=[self.constellation_list[i] + for i in range(len(self.constellation_list))] + code_book.insert(0,0+0j) + code_book=[[np.real(obs),np.imag(obs)] for obs in code_book] + + # Map averages to amplitude points + vector_cluster=vq(list_constellation,code_book) + # Subtract data points by 1 and remove 0 padding + # Neat side effect: -1 is an invalid data point + datastream=vector_cluster[0]-1 + if (datastream[0]!=-1 or datastream[-1]!=-1 + or any(datastream[1:-1]==-1)): + warnings.warn("Corrupted datastream detected while demodulating", + ModulationIntegrityWarning) + return datastream[1:-1] diff --git a/voicechat_modem_dsp/modulators/modulator_qam.pyi b/voicechat_modem_dsp/modulators/modulator_qam.pyi new file mode 100644 index 0000000..ec211a5 --- /dev/null +++ b/voicechat_modem_dsp/modulators/modulator_qam.pyi @@ -0,0 +1,14 @@ +from .modulator_base import BaseModulator +from typing import Sequence, Dict, Tuple + +from numpy import ndarray + +class QAMModulator(BaseModulator): + fs: float = ... + constellation_list: Dict[int, complex] = ... + carrier_freq: float = ... + baud: float = ... + def __init__(self, fs: float, carrier: float, + constellation_list: Sequence[complex], baud: float) -> None: ... + def modulate(self, datastream: Sequence[int]) -> ndarray: ... + def demodulate(self, modulated_data: ndarray) -> Sequence[int]: ... From 8ce34ee1947b92fd9d075eb2c433a8233d4c8181 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 29 Feb 2020 11:24:25 -0500 Subject: [PATCH 191/228] Add integrity property test for QAMModulator --- test/modulators/test_modulator_integrity.py | 39 ++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/test/modulators/test_modulator_integrity.py b/test/modulators/test_modulator_integrity.py index 5e0cb5d..ccaea26 100644 --- a/test/modulators/test_modulator_integrity.py +++ b/test/modulators/test_modulator_integrity.py @@ -5,7 +5,7 @@ from voicechat_modem_dsp.encoders.encode_pad import * from voicechat_modem_dsp.modulators import ModulationIntegrityWarning, \ - ASKModulator, FSKModulator, PSKModulator + ASKModulator, FSKModulator, PSKModulator, QAMModulator import pytest @@ -160,6 +160,43 @@ def test_property_psk_integrity(): assert bitstream==recovered_bitstream +@pytest.mark.property +def test_property_qam_integrity(): + constellation_list=[-1-1j,-1-1/3j,-1+1/3j,-1+1j, + -1/3-1j,-1/3-1/3j,-1/3+1/3j,-1/3+1j, + 1/3-1j,1/3-1/3j,1/3+1/3j,1/3+1j, + 1-1j,1-1/3j,1+1/3j,1+1j] + constellation_list=np.asarray(constellation_list) + constellation_list/=np.max(np.abs(constellation_list)) + count_run=0 + while count_run<256: + # Shuffle as opposed to complete random to test all 0x00-0xff + list_data=list(range(256)) + random.shuffle(list_data) + bitstream=bytes(list_data) + datastream=base_16_encode(bitstream) + + sampling_freq=get_rand_float(8000,48000) + carrier_freq=get_rand_float(256,sampling_freq/2) + baud_rate=get_rand_float(128,carrier_freq/4) + amplitude=get_rand_float(0.1,1) + + with warnings.catch_warnings(): + warnings.simplefilter("error",category=ModulationIntegrityWarning) + try: + modulator=QAMModulator(sampling_freq, + carrier_freq,constellation_list,baud_rate) + except ModulationIntegrityWarning: + continue + + modulated_data=modulator.modulate(datastream) + demodulated_bundle=modulator.demodulate(modulated_data) + + recovered_bitstream=base_16_decode(demodulated_bundle) + count_run+=1 + + assert bitstream==recovered_bitstream + @pytest.mark.property def test_property_fsk_integrity(): frequency_list=list(np.linspace(512,2048,16)) From e11843bb83d5aa62e2ec2e064a17b5c3a65b353d Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 5 Mar 2020 12:24:47 -0500 Subject: [PATCH 192/228] Add some parameter tests for QAMModulator --- test/modulators/test_modulator_parameters.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/modulators/test_modulator_parameters.py b/test/modulators/test_modulator_parameters.py index 6d1e01a..32c7303 100644 --- a/test/modulators/test_modulator_parameters.py +++ b/test/modulators/test_modulator_parameters.py @@ -3,7 +3,7 @@ from voicechat_modem_dsp.encoders.encode_pad import * from voicechat_modem_dsp.modulators import ModulationIntegrityWarning, \ - ASKModulator, FSKModulator, PSKModulator + ASKModulator, FSKModulator, PSKModulator, QAMModulator import pytest @@ -19,6 +19,11 @@ def test_invalid_nyquist(): with pytest.raises(ValueError, match=r"Baud is too high.+"): bad_modulator=PSKModulator(1000,100,1,np.linspace(0,np.pi,8),80) + with pytest.raises(ValueError, match=r"Carrier frequency is too high.+"): + bad_modulator=QAMModulator(1000,600,np.linspace(0.2,1,8),20) + with pytest.raises(ValueError, match=r"Baud is too high.+"): + bad_modulator=QAMModulator(1000,100,np.linspace(0.2,1,8),80) + with pytest.raises(ValueError, match=r"Baud is too high.+"): bad_modulator=FSKModulator(1000,1,np.linspace(200,1000,8),800) with pytest.raises(ValueError, match=r"Maximum frequency is too high.+"): @@ -48,6 +53,9 @@ def test_invalid_modspecific(): with pytest.warns(ModulationIntegrityWarning): bad_modulator=PSKModulator(1000,200,0.02,np.linspace(0,0.1,16),50) + with pytest.raises(ValueError, match=r"of constellation points.+"): + bad_modulator=QAMModulator(1000,200,[2,4,-2j,1+1j],50) + with pytest.raises(ValueError, match=r"Invalid frequencies.+"): bad_modulator=FSKModulator(1000,1,np.linspace(-1,1000,16),500) with pytest.raises(ValueError, match=r"amplitude must be positive.+"): From 02ad5b188bfe2ad67f22067b97e591fa80adc950 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 5 Mar 2020 12:38:01 -0500 Subject: [PATCH 193/228] Update Pipfile.lock --- Pipfile.lock | 236 ++++++++++++++++++++++++++------------------------- 1 file changed, 119 insertions(+), 117 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 12ed16c..b51f71e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -31,10 +31,10 @@ }, "clikit": { "hashes": [ - "sha256:80b0bfee42310a715773dded69590c4c33fa9fc9a351fa7c262cb67f21d0758f", - "sha256:8ae4766b974d7b1983e39d501da9a0aadf118a907a0c9b50714d027c8b59ea81" + "sha256:95394982cfa460a77ded2f173380a958e5f90c16972307c19d79b96f6e335326", + "sha256:f67336462800078e0896cf6ecfa3b460dfea4dfa01de659388a4ff0d83c8d6ca" ], - "version": "==0.4.1" + "version": "==0.4.2" }, "cycler": { "hashes": [ @@ -45,10 +45,10 @@ }, "decorator": { "hashes": [ - "sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", - "sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d" + "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", + "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" ], - "version": "==4.4.1" + "version": "==4.4.2" }, "ipykernel": { "hashes": [ @@ -60,10 +60,10 @@ }, "ipython": { "hashes": [ - "sha256:d9459e7237e2e5858738ff9c3e26504b79899b58a6d49e574d352493d80684c6", - "sha256:f6689108b1734501d3b59c84427259fd5ac5141afe2e846cfa8598eb811886c9" + "sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a", + "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333" ], - "version": "==7.12.0" + "version": "==7.13.0" }, "ipython-genutils": { "hashes": [ @@ -81,17 +81,17 @@ }, "jupyter-client": { "hashes": [ - "sha256:60e6faec1031d63df57f1cc671ed673dced0ed420f4377ea33db37b1c188b910", - "sha256:d0c077c9aaa4432ad485e7733e4d91e48f87b4f4bab7d283d42bb24cbbba0a0f" + "sha256:1fac6e3be1e797aea33d5cd1cfa568ff1ee71e01180bc89f64b24ee274f1f126", + "sha256:ed2490c65f7e0987d1e7b2c4146371d58112489e558b3a835aefb86b7283f930" ], - "version": "==5.3.4" + "version": "==6.0.0" }, "jupyter-core": { "hashes": [ - "sha256:464769f7387d7a62a2403d067f1ddc616655b7f77f5d810c0dd62cb54bfd0fb9", - "sha256:a183e0ec2e8f6adddf62b0a3fc6a2237e3e0056d381e536d3e7c7ecc3067e244" + "sha256:394fd5dd787e7c8861741880bdf8a00ce39f95de5d18e579c74b882522219e7e", + "sha256:a4ee613c060fe5697d913416fc9d553599c05e4492d58fac1192c9a6844abb21" ], - "version": "==4.6.1" + "version": "==4.6.3" }, "kiwisolver": { "hashes": [ @@ -137,23 +137,23 @@ }, "matplotlib": { "hashes": [ - "sha256:23b71560c721109954c0215ffc81f4c80ce8528749d534a01a61e8ab737c5bce", - "sha256:4164265ca573481ce61c83322e6b33628203afeabeb3e22c50376f5d3ee0f9be", - "sha256:470eed601ff5132364e0121a20d7c3d43fab969c8c333422c1b6b72fde2ed3c1", - "sha256:635ded7834f43c8d999076236f7e90074d77f7b8345e5e82cd95af053cc29df1", - "sha256:6a0031774c6c68298183438edf2e738856d63a4c4797876fa81d0ee337f5361c", - "sha256:78d0772412c0653aa3e860c52ff08d1f5ba64334e2b86b09dc2d502657d8ca73", - "sha256:8efff896c49676700dc6adace6137a854ff64a4d44ca057ff726960ffdaa47bf", - "sha256:97f04d29a358826f205320fbc88d46ce5c5ff6fb54ae050042ff396beda52ca4", - "sha256:b4c0010eff09ab65c77ad1a0eec6c7cccb9f6838c3c77dc5b4002fe0cf2912fd", - "sha256:b5ace0531255932ad19fe64c116ada2713f7b38381db8f68df0fa694409e67d1", - "sha256:c7bb7ed3e011324b56462391ec3f4bbb7c8c6af5892ebfb45d312b15b4cdfc8d", - "sha256:db3121f12fb9b99f105d1413aebaeb3d943f269f3d262b45586d12765866f0c6", - "sha256:db8bbba9284845034a2f0e1add91dc5e89db8c996359bdcf677a8d6f88875cf1", - "sha256:f0023322c99328c40ce22678ab0ab5adfc27e338419966539398239996f63e8d" + "sha256:0711b07920919951b2c508a773c433cbe07bdad952ea84ed9d18ca7853ccbe8b", + "sha256:0ab307e610302971012dc2387c97fc68e58c8eb00045a2c735da1b16353a3e3f", + "sha256:651d76daf9168250370d4befb09f79875daa2224a9096d97dfc3ed764c842be4", + "sha256:8e931015769322ee6860cabb8f975f628788e851092fd5edbdb065b5a516e3af", + "sha256:97a03e73f9ab71db8e4084894550c3af420c8ab1989b5e1306261b17576bf61b", + "sha256:9d174cc9681184023a7d520079eb0c085208761c6562710c1de7263d08217ab6", + "sha256:b21479a4478070c1c0f460e1bf1b65341e6a70ae0da905fcee836651450c66bb", + "sha256:b93377c6720e7db9cbba57e856a21aae2ff707677a6ee6b3b9d485f22ed82697", + "sha256:be937f34047bc09ed22d6a19d970fdc61d5d3191aa62f3262fc7f308e6d2e7f9", + "sha256:d281862a68b0bfce8f9e02a8e5acaa5cfbec37f37320f59b52eaf54b6423ec13", + "sha256:d5287cfcabad6f0f71a2627c1bbb6fb0cddacb9844f6c91f210604faa508f562", + "sha256:d75f5e952562f5e494ae92c1f917fc96c2ce09305a7c1bdc2e6502d3c61fbdc3", + "sha256:ee8acb1d4ee204e5cfe361d8f00d7e52c68f81c099b6c6048a3c76bf2c6b46e6", + "sha256:fc84f7c7cf1c5a9dbceadb7546818228f019d3b113ce5e362120c895fbba2944" ], "index": "pypi", - "version": "==3.1.3" + "version": "==3.2.0" }, "numpy": { "hashes": [ @@ -184,17 +184,17 @@ }, "parso": { "hashes": [ - "sha256:56b2105a80e9c4df49de85e125feb6be69f49920e121406f15e7acde6c9dfc57", - "sha256:951af01f61e6dccd04159042a0706a31ad437864ec6e25d0d7a96a9fbb9b0095" + "sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157", + "sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995" ], - "version": "==0.6.1" + "version": "==0.6.2" }, "pastel": { "hashes": [ - "sha256:a904e1659512cc9880a028f66de77cc813a4c32f7ceb68725cbc8afad57ef7ef", - "sha256:bf3b1901b2442ea0d8ab9a390594e5b0c9584709d543a3113506fe8b28cbace3" + "sha256:18b559dc3ad4ba9b8bd5baebe6503f25f36d21460f021cf27a8d889cb5d17840", + "sha256:46155fc523bdd4efcd450bbcb3f2b94a6e3b25edc0eb493e081104ad09e1ca36" ], - "version": "==0.1.1" + "version": "==0.2.0" }, "pexpect": { "hashes": [ @@ -280,43 +280,43 @@ }, "pyzmq": { "hashes": [ - "sha256:01b588911714a6696283de3904f564c550c9e12e8b4995e173f1011755e01086", - "sha256:0573b9790aa26faff33fba40f25763657271d26f64bffb55a957a3d4165d6098", - "sha256:0fa82b9fc3334478be95a5566f35f23109f763d1669bb762e3871a8fa2a4a037", - "sha256:1e59b7b19396f26e360f41411a5d4603356d18871049cd7790f1a7d18f65fb2c", - "sha256:2a294b4f44201bb21acc2c1a17ff87fbe57b82060b10ddb00ac03e57f3d7fcfa", - "sha256:355b38d7dd6f884b8ee9771f59036bcd178d98539680c4f87e7ceb2c6fd057b6", - "sha256:4b73d20aec63933bbda7957e30add233289d86d92a0bb9feb3f4746376f33527", - "sha256:4ec47f2b50bdb97df58f1697470e5c58c3c5109289a623e30baf293481ff0166", - "sha256:5541dc8cad3a8486d58bbed076cb113b65b5dd6b91eb94fb3e38a3d1d3022f20", - "sha256:6fca7d11310430e751f9832257866a122edf9d7b635305c5d8c51f74a5174d3d", - "sha256:7369656f89878455a5bcd5d56ca961884f5d096268f71c0750fc33d6732a25e5", - "sha256:75d73ee7ca4b289a2a2dfe0e6bd8f854979fc13b3fe4ebc19381be3b04e37a4a", - "sha256:80c928d5adcfa12346b08d31360988d843b54b94154575cccd628f1fe91446bc", - "sha256:83ce18b133dc7e6789f64cb994e7376c5aa6b4aeced993048bf1d7f9a0fe6d3a", - "sha256:8b8498ceee33a7023deb2f3db907ca41d6940321e282297327a9be41e3983792", - "sha256:8c69a6cbfa94da29a34f6b16193e7c15f5d3220cb772d6d17425ff3faa063a6d", - "sha256:8ff946b20d13a99dc5c21cb76f4b8b253eeddf3eceab4218df8825b0c65ab23d", - "sha256:972d723a36ab6a60b7806faa5c18aa3c080b7d046c407e816a1d8673989e2485", - "sha256:a6c9c42bbdba3f9c73aedbb7671815af1943ae8073e532c2b66efb72f39f4165", - "sha256:aa3872f2ebfc5f9692ef8957fe69abe92d905a029c0608e45ebfcd451ad30ab5", - "sha256:cf08435b14684f7f2ca2df32c9df38a79cdc17c20dc461927789216cb43d8363", - "sha256:d30db4566177a6205ed1badb8dbbac3c043e91b12a2db5ef9171b318c5641b75", - "sha256:d5ac84f38575a601ab20c1878818ffe0d09eb51d6cb8511b636da46d0fd8949a", - "sha256:e37f22eb4bfbf69cd462c7000616e03b0cdc1b65f2d99334acad36ea0e4ddf6b", - "sha256:e6549dd80de7b23b637f586217a4280facd14ac01e9410a037a13854a6977299", - "sha256:ed6205ca0de035f252baa0fd26fdd2bc8a8f633f92f89ca866fd423ff26c6f25", - "sha256:efdde21febb9b5d7a8e0b87ea2549d7e00fda1936459cfb27fb6fca0c36af6c1", - "sha256:f4e72646bfe79ff3adbf1314906bbd2d67ef9ccc71a3a98b8b2ccbcca0ab7bec" - ], - "version": "==18.1.1" + "sha256:0bbc1728fe4314b4ca46249c33873a390559edac7c217ec7001b5e0c34a8fb7f", + "sha256:1e076ad5bd3638a18c376544d32e0af986ca10d43d4ce5a5d889a8649f0d0a3d", + "sha256:242d949eb6b10197cda1d1cec377deab1d5324983d77e0d0bf9dc5eb6d71a6b4", + "sha256:26f4ae420977d2a8792d7c2d7bda43128b037b5eeb21c81951a94054ad8b8843", + "sha256:32234c21c5e0a767c754181c8112092b3ddd2e2a36c3f76fc231ced817aeee47", + "sha256:3f12ce1e9cc9c31497bd82b207e8e86ccda9eebd8c9f95053aae46d15ccd2196", + "sha256:4557d5e036e6d85715b4b9fdb482081398da1d43dc580d03db642b91605b409f", + "sha256:4f562dab21c03c7aa061f63b147a595dbe1006bf4f03213272fc9f7d5baec791", + "sha256:5e071b834051e9ecb224915398f474bfad802c2fff883f118ff5363ca4ae3edf", + "sha256:5e1f65e576ab07aed83f444e201d86deb01cd27dcf3f37c727bc8729246a60a8", + "sha256:5f10a31f288bf055be76c57710807a8f0efdb2b82be6c2a2b8f9a61f33a40cea", + "sha256:6aaaf90b420dc40d9a0e1996b82c6a0ff91d9680bebe2135e67c9e6d197c0a53", + "sha256:75238d3c16cab96947705d5709187a49ebb844f54354cdf0814d195dd4c045de", + "sha256:7f7e7b24b1d392bb5947ba91c981e7d1a43293113642e0d8870706c8e70cdc71", + "sha256:84b91153102c4bcf5d0f57d1a66a0f03c31e9e6525a5f656f52fc615a675c748", + "sha256:944f6bb5c63140d76494467444fd92bebd8674236837480a3c75b01fe17df1ab", + "sha256:a1f957c20c9f51d43903881399b078cddcf710d34a2950e88bce4e494dcaa4d1", + "sha256:a49fd42a29c1cc1aa9f461c5f2f5e0303adba7c945138b35ee7f4ab675b9f754", + "sha256:a99ae601b4f6917985e9bb071549e30b6f93c72f5060853e197bdc4b7d357e5f", + "sha256:ad48865a29efa8a0cecf266432ea7bc34e319954e55cf104be0319c177e6c8f5", + "sha256:b08e425cf93b4e018ab21dc8fdbc25d7d0502a23cc4fea2380010cf8cf11e462", + "sha256:bb10361293d96aa92be6261fa4d15476bca56203b3a11c62c61bd14df0ef89ba", + "sha256:bd1a769d65257a7a12e2613070ca8155ee348aa9183f2aadf1c8b8552a5510f5", + "sha256:cb3b7156ef6b1a119e68fbe3a54e0a0c40ecacc6b7838d57dd708c90b62a06dc", + "sha256:e8e4efb52ec2df8d046395ca4c84ae0056cf507b2f713ec803c65a8102d010de", + "sha256:f37c29da2a5b0c5e31e6f8aab885625ea76c807082f70b2d334d3fd573c3100a", + "sha256:f4d558bc5668d2345773a9ff8c39e2462dafcb1f6772a2e582fbced389ce527f", + "sha256:f5b6d015587a1d6f582ba03b226a9ddb1dfb09878b3be04ef48b01b7d4eb6b2a" + ], + "version": "==19.0.0" }, "ruamel.yaml": { "hashes": [ - "sha256:9d59fa89985c55155d35c886663e357813404ae8f94638cb673135b8c8c1a7c7", - "sha256:dba517a7e330b6caf476b757022d21efa13c32694bfba1e057ce59a374f18f0a" + "sha256:0962fd7999e064c4865f96fb1e23079075f4a2a14849bcdc5cdba53a24f9759b", + "sha256:099c644a778bf72ffa00524f78dd0b6476bca94a1da344130f4bf3381ce5b954" ], - "version": "==0.16.7" + "version": "==0.16.10" }, "ruamel.yaml.clib": { "hashes": [ @@ -340,7 +340,7 @@ "sha256:ed5b3698a2bb241b7f5cbbe277eaa7fe48b07a58784fba4f75224fd066d253ad", "sha256:f9dcc1ae73f36e8059589b601e8e4776b9976effd76c21ad6a855a74318efd6e" ], - "markers": "platform_python_implementation == 'CPython' and python_version < '3.8'", + "markers": "platform_python_implementation == 'CPython' and python_version < '3.9'", "version": "==0.2.0" }, "scipy": { @@ -386,15 +386,17 @@ }, "tornado": { "hashes": [ - "sha256:349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c", - "sha256:398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60", - "sha256:4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281", - "sha256:559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5", - "sha256:abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7", - "sha256:c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9", - "sha256:c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5" + "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc", + "sha256:22aed82c2ea340c3771e3babc5ef220272f6fd06b5108a53b4976d0d722bcd52", + "sha256:2c027eb2a393d964b22b5c154d1a23a5f8727db6fda837118a776b29e2b8ebc6", + "sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d", + "sha256:5618f72e947533832cbc3dec54e1dffc1747a5cb17d1fd91577ed14fa0dc081b", + "sha256:5f6a07e62e799be5d2330e68d808c8ac41d4a259b9cea61da4101b83cb5dc673", + "sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9", + "sha256:c952975c8ba74f546ae6de2e226ab3cc3cc11ae47baf607459a6728585bb542a", + "sha256:c98232a3ac391f5faea6821b53db8db461157baa788f5d6222a193e9456e1740" ], - "version": "==6.0.3" + "version": "==6.0.4" }, "traitlets": { "hashes": [ @@ -435,10 +437,10 @@ }, "bleach": { "hashes": [ - "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", - "sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa" + "sha256:44f69771e2ac81ff30d929d485b7f9919f3ad6d019b6b20c74f3b8687c3f70df", + "sha256:aa8b870d0f46965bac2c073a93444636b0e1ca74e9777e34f03dd494b8a59d48" ], - "version": "==3.1.0" + "version": "==3.1.1" }, "certifi": { "hashes": [ @@ -493,10 +495,10 @@ }, "decorator": { "hashes": [ - "sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", - "sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d" + "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", + "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" ], - "version": "==4.4.1" + "version": "==4.4.2" }, "defusedxml": { "hashes": [ @@ -521,10 +523,10 @@ }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], - "version": "==2.8" + "version": "==2.9" }, "imagesize": { "hashes": [ @@ -572,10 +574,10 @@ }, "jupyter-core": { "hashes": [ - "sha256:464769f7387d7a62a2403d067f1ddc616655b7f77f5d810c0dd62cb54bfd0fb9", - "sha256:a183e0ec2e8f6adddf62b0a3fc6a2237e3e0056d381e536d3e7c7ecc3067e244" + "sha256:394fd5dd787e7c8861741880bdf8a00ce39f95de5d18e579c74b882522219e7e", + "sha256:a4ee613c060fe5697d913416fc9d553599c05e4492d58fac1192c9a6844abb21" ], - "version": "==4.6.1" + "version": "==4.6.3" }, "markupsafe": { "hashes": [ @@ -712,10 +714,10 @@ }, "packaging": { "hashes": [ - "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", - "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334" + "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", + "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" ], - "version": "==20.1" + "version": "==20.3" }, "pandocfilters": { "hashes": [ @@ -774,10 +776,10 @@ }, "requests": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], - "version": "==2.22.0" + "version": "==2.23.0" }, "six": { "hashes": [ @@ -795,32 +797,32 @@ }, "sphinx": { "hashes": [ - "sha256:b3feeed2da588adabd0db5329a309f27317e98382122721511e901833d092028", - "sha256:fb2ce74c28154872757925edda0060b4c4102cdc86b8b30063f23399678de2ca" + "sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66", + "sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb" ], "index": "pypi", - "version": "==2.4.0" + "version": "==2.4.4" }, "sphinxcontrib-applehelp": { "hashes": [ - "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", - "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d" + "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", + "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" ], - "version": "==1.0.1" + "version": "==1.0.2" }, "sphinxcontrib-devhelp": { "hashes": [ - "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", - "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981" + "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", + "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" ], - "version": "==1.0.1" + "version": "==1.0.2" }, "sphinxcontrib-htmlhelp": { "hashes": [ - "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", - "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7" + "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f", + "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b" ], - "version": "==1.0.2" + "version": "==1.0.3" }, "sphinxcontrib-jsmath": { "hashes": [ @@ -831,17 +833,17 @@ }, "sphinxcontrib-qthelp": { "hashes": [ - "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", - "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f" + "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", + "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" ], - "version": "==1.0.2" + "version": "==1.0.3" }, "sphinxcontrib-serializinghtml": { "hashes": [ - "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", - "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768" + "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc", + "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a" ], - "version": "==1.1.3" + "version": "==1.1.4" }, "testpath": { "hashes": [ @@ -914,10 +916,10 @@ }, "zipp": { "hashes": [ - "sha256:5c56e330306215cd3553342cfafc73dda2c60792384117893f3a83f8a1209f50", - "sha256:d65287feb793213ffe11c0f31b81602be31448f38aeb8ffc2eb286c4f6f6657e" + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "version": "==2.2.0" + "version": "==3.1.0" } } } From c931816fdf54c2525f7832276b335acac90cb76d Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 10 Mar 2020 12:05:41 -0400 Subject: [PATCH 194/228] Test more of QAM and add some CLI stuff --- test/modulators/test_modulator_parameters.py | 4 ++++ voicechat_modem_dsp/cli/command_objects.py | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/test/modulators/test_modulator_parameters.py b/test/modulators/test_modulator_parameters.py index 32c7303..3e8fea9 100644 --- a/test/modulators/test_modulator_parameters.py +++ b/test/modulators/test_modulator_parameters.py @@ -55,6 +55,10 @@ def test_invalid_modspecific(): with pytest.raises(ValueError, match=r"of constellation points.+"): bad_modulator=QAMModulator(1000,200,[2,4,-2j,1+1j],50) + with pytest.warns(ModulationIntegrityWarning): + bad_modulator=QAMModulator(2000,880,np.geomspace(0.2,1,256),40) + with pytest.warns(ModulationIntegrityWarning): + bad_modulator=QAMModulator(2000,880,[0.05,1j],80) with pytest.raises(ValueError, match=r"Invalid frequencies.+"): bad_modulator=FSKModulator(1000,1,np.linspace(-1,1000,16),500) diff --git a/voicechat_modem_dsp/cli/command_objects.py b/voicechat_modem_dsp/cli/command_objects.py index dcd7931..e851a60 100644 --- a/voicechat_modem_dsp/cli/command_objects.py +++ b/voicechat_modem_dsp/cli/command_objects.py @@ -7,7 +7,7 @@ from .config_loader import construct_modulators from .command_utils import CLIError, ExtendedCommand -from ..modulators import ASKModulator, PSKModulator, FSKModulator +from ..modulators import ASKModulator, PSKModulator, QAMModulator, FSKModulator from ..encoders import encode_function_mappings, decode_function_mappings from ..encoders.ecc import hamming_7_4 @@ -86,6 +86,8 @@ def handle(self): constellation_length=len(modulator_obj.freq_list) elif isinstance(modulator_obj,PSKModulator): constellation_length=len(modulator_obj.phase_list) + elif isinstance(modulator_obj,QAMModulator): + constellation_length=len(modulator_obj.constellation_list) else: raise CLIError("Modulator type is not yet supported.") @@ -162,6 +164,8 @@ def handle(self): constellation_length=len(modulator_obj.freq_list) elif isinstance(modulator_obj,PSKModulator): constellation_length=len(modulator_obj.phase_list) + elif isinstance(modulator_obj,QAMModulator): + constellation_length=len(modulator_obj.constellation_list) else: raise CLIError("Modulator type is not yet supported.") From 3d4434111ef85e87f41e772c5629254a6c0687d8 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 10 Mar 2020 12:11:57 -0400 Subject: [PATCH 195/228] Add QAM section to config loader --- voicechat_modem_dsp/cli/config_loader.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/cli/config_loader.py b/voicechat_modem_dsp/cli/config_loader.py index 5fcbb80..80c13f8 100644 --- a/voicechat_modem_dsp/cli/config_loader.py +++ b/voicechat_modem_dsp/cli/config_loader.py @@ -3,7 +3,8 @@ from strictyaml import load, YAML, YAMLValidationError from .yaml_schema_validators import Complex -from ..modulators import ASKModulator, PSKModulator, FSKModulator, BaseModulator +from ..modulators import ASKModulator, PSKModulator, QAMModulator, \ + FSKModulator, BaseModulator from typing import List as TypingList from typing import Mapping as TypingMap @@ -82,7 +83,9 @@ def construct_modulators(config_dict: TypingMap) -> TypingList[BaseModulator]: phases=[2*pi*phase for phase in phases] modulator_list.append(PSKModulator(fs, carrier, 1, phases, baud)) elif mode=="qam": - pass + carrier=modulator_config["carrier"] + constellation=modulator_config["constellation"] + modulator_list.append(QAMModulator(fs, carrier, constellation, baud)) elif mode=="fsk": amplitude=modulator_config["amplitude"] frequencies=modulator_config["frequencies"] From 2b69d195aeec25f60f68a0ed13653cb92c74516b Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 16 Mar 2020 18:51:12 -0400 Subject: [PATCH 196/228] Add a QAM modulator to the example configs --- docs/specs/config.rst | 8 +++++++- docs/specs/examples/qam_1k.yaml | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 docs/specs/examples/qam_1k.yaml diff --git a/docs/specs/config.rst b/docs/specs/config.rst index a66b33b..094fb6f 100644 --- a/docs/specs/config.rst +++ b/docs/specs/config.rst @@ -96,7 +96,13 @@ Example Files This modulation is similar to the amateur radio PSK31 mode. However, it uses a more rudimentary Hamming 7,4 ECC and Gaussian pulse shaping, as opposed to -a raised cosine filter. +a raised cosine filter and more advanced ECC methods. .. literalinclude:: examples/psk31.yaml + :language: yaml + +2048 bits/second QAM Modulation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. literalinclude:: examples/qam_1k.yaml :language: yaml \ No newline at end of file diff --git a/docs/specs/examples/qam_1k.yaml b/docs/specs/examples/qam_1k.yaml new file mode 100644 index 0000000..3a40994 --- /dev/null +++ b/docs/specs/examples/qam_1k.yaml @@ -0,0 +1,8 @@ +version: 0.1 +fs: 48000 +ecc: none +modulators: + - mode: qam + baud: 256 + carrier: 1000 + constellation: 0.5+0.5i,0.5,0.5-0.5i,0.5i,-0.5i,-0.5+0.5i,-0.5,-0.5-0.5i From 72905220d9f3ac0217e46bb1d6323554406e6d89 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 16 Mar 2020 18:51:50 -0400 Subject: [PATCH 197/228] Include QAMModulator assertion in test_yaml_loader --- test/cli/test_yaml_loader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/cli/test_yaml_loader.py b/test/cli/test_yaml_loader.py index edce655..1696913 100644 --- a/test/cli/test_yaml_loader.py +++ b/test/cli/test_yaml_loader.py @@ -2,7 +2,7 @@ construct_modulators from voicechat_modem_dsp.cli.command_utils import CLIError, ExtendedCommand from voicechat_modem_dsp.modulators import ASKModulator, PSKModulator, \ - FSKModulator + QAMModulator, FSKModulator from .testing_utils import DirectoryChanger, FileCleanup import glob @@ -30,7 +30,7 @@ def test_load_doc_examples(): elif "psk" in yaml_file: assert isinstance(modulator_list[0],PSKModulator) elif "qam" in yaml_file: - pass + assert isinstance(modulator_list[0],QAMModulator) @pytest.mark.unit def test_load_doc_examples_staticmethod(): @@ -50,7 +50,7 @@ def test_load_doc_examples_staticmethod(): elif "psk" in yaml_file: assert isinstance(modulator_list[0],PSKModulator) elif "qam" in yaml_file: - pass + assert isinstance(modulator_list[0],QAMModulator) @pytest.mark.unit def test_load_doc_nonexamples(): From 547fec36f7ec4f47563d4299d802fc7b8d5642de Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 16 Mar 2020 18:53:00 -0400 Subject: [PATCH 198/228] Add more complex number parsing validation tests --- test/cli/test_custom_validators.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/cli/test_custom_validators.py b/test/cli/test_custom_validators.py index da9855d..1a881eb 100644 --- a/test/cli/test_custom_validators.py +++ b/test/cli/test_custom_validators.py @@ -10,13 +10,24 @@ def generate_yaml(val): return "complex: {}".format(val) @pytest.mark.unit -def test_complex_cartesian_valid(): +def test_complex_valid(): yaml_str=generate_yaml(1+1j) yaml_obj=load(yaml_str,schema=complex_schema) assert yaml_obj["complex"].data==1+1j yaml_str_2=generate_yaml("(1,0.5)") yaml_obj_2=load(yaml_str_2,schema=complex_schema) assert yaml_obj_2["complex"].data.real==-1 + assert abs(yaml_obj_2["complex"].data.imag-0)<1e-10 + +@pytest.mark.unit +def test_complex_pure_valid(): + yaml_str=generate_yaml(1) + yaml_obj=load(yaml_str,schema=complex_schema) + assert yaml_obj["complex"].data==1 + yaml_str_2=generate_yaml(1j) + yaml_obj_2=load(yaml_str_2,schema=complex_schema) + assert yaml_obj_2["complex"].data.real==0 + assert yaml_obj_2["complex"].data.imag==1 @pytest.mark.unit def test_complex_invalid(): From 0a18f76f6257d51dc7fc73cda859511dd3059c50 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 16 Mar 2020 18:53:25 -0400 Subject: [PATCH 199/228] Use edge extension vs constant 0 extension for FSK edge samples --- voicechat_modem_dsp/modulators/modulator_fsk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index 12ce749..4bb6a6b 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -57,7 +57,7 @@ def modulate(self, datastream): # Map datastream to frequencies and pad on both ends # Exact padding does not matter because of amplitude shaping frequency_data = np.pad([self.freq_list[datum] for datum in datastream], - 1,mode="constant",constant_values=0) + 1,mode="edge") # Upsample frequency to actual sampling rate interp_sample_count=int(np.ceil( From bd5db47880a8211d8a2b20654585ba18205ea64e Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 16 Mar 2020 20:32:52 -0400 Subject: [PATCH 200/228] Begin writing a QAM Modulation notebook --- docs/index.rst | 1 + docs/notebooks/QAM Modulator.ipynb | 2468 ++++++++++++++++++++++++++++ 2 files changed, 2469 insertions(+) create mode 100644 docs/notebooks/QAM Modulator.ipynb diff --git a/docs/index.rst b/docs/index.rst index 79c39ab..95f4176 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,6 +11,7 @@ Welcome to Voicechat Modem (DSP Component)'s documentation! :caption: Contents: notebooks/Pre-Modulation Gaussian Smoothing + notebooks/QAM Modulator notebooks/FSK Modulator specs/config diff --git a/docs/notebooks/QAM Modulator.ipynb b/docs/notebooks/QAM Modulator.ipynb new file mode 100644 index 0000000..c11e85b --- /dev/null +++ b/docs/notebooks/QAM Modulator.ipynb @@ -0,0 +1,2468 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# QAM Modulation and Demodulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "QAM Modulation varies the amplitude and phase of a signal (or equivalently its in-phase component and its quadrature component) to modulate information, and can be demodulated using a product demodulator. (Both ASK and PSK modulation can be handled as a special case of QAM Modulation)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from scipy import signal\n", + "\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import set_matplotlib_formats\n", + "%matplotlib inline\n", + "set_matplotlib_formats('svg')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The central identity behind these modulation modes demonstrates the frequency content of the product of two sinusoidal functions:\n", + "\n", + "\\begin{align*}\n", + "e^{j\\omega_1 t} \\cos(\\omega_2 t) &= e^{j\\omega_1 t} \\frac{e^{j\\omega_2 t}+e^{-j\\omega_2 t}}{2} \\\\\n", + "&=\\frac{1}{2}(e^{jt(\\omega_1+\\omega_2)}+e^{jt(\\omega_1-\\omega_2)})\n", + "\\end{align*}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We demonstrate this with plots below:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "timearray=np.arange(0,4,0.01)\n", + "carrier=np.cos(2*np.pi*6*timearray)\n", + "example_sig=0.5*np.cos(2*np.pi*timearray)\n", + "modulated_example_sig=carrier*example_sig" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(timearray,carrier,\n", + " timearray,example_sig,\n", + " timearray,modulated_example_sig)\n", + "plt.legend([\"Carrier\",\"Raw Signal\",\"Modulated Signal\"])\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "example_sig_fft=np.fft.rfft(example_sig)\n", + "modulated_example_sig_fft=np.fft.rfft(modulated_example_sig)\n", + "example_freqs=np.fft.rfftfreq(len(example_sig),d=0.01)\n", + "plt.plot(example_freqs,np.abs(example_sig_fft),\n", + " example_freqs,np.abs(modulated_example_sig_fft))\n", + "plt.vlines([6],0,100)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We express the product modulation as an analytic signal:\n", + "\n", + "$$e^{2 \\pi j f t}\\left(I(t)+jQ(t)\\right)=\n", + "I(t)\\cos(2 \\pi f t)-Q(t)\\sin(2 \\pi f t)\n", + "+jI(t)\\sin(2 \\pi f t)+jQ(t)\\cos(2 \\pi f t)$$\n", + "\n", + "so\n", + "\n", + "$$\\Re\\left(e^{2 \\pi j f t}\\left(I(t)+jQ(t)\\right)\\right)=\n", + "I(t)\\cos(2 \\pi f t)-Q(t)\\sin(2 \\pi f t)$$\n", + "\n", + "We also note that $e^{2 \\pi j f t}\\left(I(t)+jQ(t)\\right)$ has only positive frequency components, and that the real part will have an even Fourier Transform. This demonstrates the information recovery aspect of an analytic signal, including the fact that the real part contains enough information to reconstruct both $I(t)$ and $Q(t)$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Demodulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We demodulate my multiplying by the carrier, producing\n", + "\n", + "$$e^{2 \\pi j f t} \\left(I(t)\\cos(2 \\pi f t)-Q(t)\\sin(2 \\pi f t) \\right)=\\frac{1}{2}(I(t)-jQ(t))+k\\left(e^{4\\pi jft}\\right)$$\n", + "\n", + "The high frequency terms are filtered out, leaving $\\frac{1}{2}(I(t)-jQ(t))$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Environment (virtualenv_voicechat-modem-dsp-ciksczpw)", + "language": "python", + "name": "virtualenv_voicechat-modem-dsp-ciksczpw" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 7a326c9c5394c8e45655eb1776334ffa9d6c5a96 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 7 Apr 2020 11:38:30 -0700 Subject: [PATCH 201/228] Finish writing QAM Modulator notebook --- docs/notebooks/QAM Modulator.ipynb | 6085 +++++++++++++++++++++++++++- 1 file changed, 6034 insertions(+), 51 deletions(-) diff --git a/docs/notebooks/QAM Modulator.ipynb b/docs/notebooks/QAM Modulator.ipynb index c11e85b..f8eaff1 100644 --- a/docs/notebooks/QAM Modulator.ipynb +++ b/docs/notebooks/QAM Modulator.ipynb @@ -110,10 +110,10 @@ " \n", " \n", + "\" id=\"mb8199f048a\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -157,7 +157,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -198,7 +198,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -228,7 +228,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -243,7 +243,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -284,7 +284,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -299,7 +299,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -348,7 +348,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -363,7 +363,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -401,10 +401,10 @@ " \n", " \n", + "\" id=\"mb65fdfdff8\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -429,7 +429,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -457,7 +457,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -474,7 +474,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -491,7 +491,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -507,7 +507,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -523,7 +523,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -539,7 +539,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -555,7 +555,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -570,7 +570,7 @@ " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1920,10 +1920,10 @@ " \n", " \n", + "\" id=\"m30078d2426\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1959,7 +1959,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1988,7 +1988,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2028,7 +2028,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2076,7 +2076,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2109,7 +2109,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2153,10 +2153,10 @@ " \n", " \n", + "\" id=\"m426211fbea\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2169,7 +2169,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2183,7 +2183,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2197,7 +2197,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2243,7 +2243,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2298,7 +2298,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2312,12 +2312,12 @@ " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2413,27 +2413,6010 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "base_signal=np.concatenate([np.linspace(0,1,30),\n", + " np.linspace(1,1j,len(timearray)-60),\n", + " np.linspace(1j,0,30)])\n", + "plt.plot(timearray,np.real(base_signal),timearray,np.imag(base_signal))\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "complex_carrier=np.exp(2*1j*np.pi*6*timearray)\n", + "transmitted_signal=np.real(base_signal*complex_carrier)\n", + "plt.plot(timearray,transmitted_signal)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Demodulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We demodulate my multiplying by the carrier, producing\n", + "\n", + "$$e^{2 \\pi j f t} \\left(I(t)\\cos(2 \\pi f t)-Q(t)\\sin(2 \\pi f t) \\right)=\\frac{1}{2}(I(t)-jQ(t))+k\\left(e^{4\\pi jft}\\right)$$\n", + "\n", + "The high frequency terms can be filtered out to leave behind $\\frac{1}{2}(I(t)-jQ(t))$." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "demodulated_signal=transmitted_signal*complex_carrier\n", + "demodulated_signal=2*np.conj(demodulated_signal)\n", + "plt.plot(timearray,np.real(demodulated_signal),timearray,np.imag(demodulated_signal))\n", + "plt.plot(timearray,np.real(base_signal),timearray,np.imag(base_signal))\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "filt=signal.firls(51,[0,4,8,50],[1,1,0,0],fs=100)\n", + "freqresp,resp=signal.freqz(filt,1,fs=100)\n", + "plt.semilogy(freqresp,np.abs(resp))\n", + "plt.show()\n", + "plt.plot(freqresp,np.unwrap(np.angle(resp)))\n", + "plt.show()" + ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 10, "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ - "## Demodulation" + "filtered_demodulated_signal=signal.lfilter(filt,1,demodulated_signal)\n", + "plt.plot(timearray,np.real(filtered_demodulated_signal),\n", + " timearray,np.imag(filtered_demodulated_signal))\n", + "plt.plot(timearray+25*0.01,np.real(base_signal),timearray+25*0.01,np.imag(base_signal))\n", + "plt.show()" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 11, "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ - "We demodulate my multiplying by the carrier, producing\n", + "original_fft=np.fft.fftshift(np.fft.fft(base_signal))\n", + "transmitted_fft=np.fft.fftshift(np.fft.fft(transmitted_signal))\n", + "demodulated_fft=np.fft.fftshift(np.fft.fft(demodulated_signal))\n", + "filtered_demodulated_fft=np.fft.fftshift(np.fft.fft(filtered_demodulated_signal))\n", "\n", - "$$e^{2 \\pi j f t} \\left(I(t)\\cos(2 \\pi f t)-Q(t)\\sin(2 \\pi f t) \\right)=\\frac{1}{2}(I(t)-jQ(t))+k\\left(e^{4\\pi jft}\\right)$$\n", + "frequencies=np.fft.fftshift(np.fft.fftfreq(len(base_signal),0.01))\n", "\n", - "The high frequency terms are filtered out, leaving $\\frac{1}{2}(I(t)-jQ(t))$." + "plt.plot(frequencies,np.abs(original_fft),\n", + " frequencies,np.abs(transmitted_fft),\n", + " frequencies,np.abs(demodulated_fft),\n", + " frequencies,np.abs(filtered_demodulated_fft))\n", + "plt.show()" ] }, { From 14141b7e249016e4cc0f60289bc0ddc1e4ef2322 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 10 May 2020 23:58:17 -0700 Subject: [PATCH 202/228] Add positive frequency check to all modulators --- test/modulators/test_modulator_parameters.py | 2 +- voicechat_modem_dsp/modulators/modulator_ask.py | 2 ++ voicechat_modem_dsp/modulators/modulator_fsk.py | 2 +- voicechat_modem_dsp/modulators/modulator_psk.py | 2 ++ voicechat_modem_dsp/modulators/modulator_qam.py | 2 ++ 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/test/modulators/test_modulator_parameters.py b/test/modulators/test_modulator_parameters.py index 3e8fea9..31d0486 100644 --- a/test/modulators/test_modulator_parameters.py +++ b/test/modulators/test_modulator_parameters.py @@ -60,7 +60,7 @@ def test_invalid_modspecific(): with pytest.warns(ModulationIntegrityWarning): bad_modulator=QAMModulator(2000,880,[0.05,1j],80) - with pytest.raises(ValueError, match=r"Invalid frequencies.+"): + with pytest.raises(ValueError, match=r"Frequencies.+positive.?"): bad_modulator=FSKModulator(1000,1,np.linspace(-1,1000,16),500) with pytest.raises(ValueError, match=r"amplitude must be positive.+"): bad_modulator=FSKModulator(1000,-1,np.linspace(100,200,16),20) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 3185db2..a679939 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -11,6 +11,8 @@ class ASKModulator(BaseModulator): def __init__(self, fs, carrier, amp_list, baud): + if carrier<=0: + raise ValueError("Frequency of carrier must be positive") if baud>=0.5*carrier: raise ValueError("Baud is too high to be modulated "+ "using carrier frequency") diff --git a/voicechat_modem_dsp/modulators/modulator_fsk.py b/voicechat_modem_dsp/modulators/modulator_fsk.py index 4bb6a6b..03ea201 100644 --- a/voicechat_modem_dsp/modulators/modulator_fsk.py +++ b/voicechat_modem_dsp/modulators/modulator_fsk.py @@ -13,7 +13,7 @@ class FSKModulator(BaseModulator): def __init__(self, fs, amplitude, freq_list, baud): if min(freq_list)<=0: - raise ValueError("Invalid frequencies given") + raise ValueError("Frequencies must be positive") if baud>min(freq_list): raise ValueError("Baud is too high to be modulated "+ "using given frequencies") diff --git a/voicechat_modem_dsp/modulators/modulator_psk.py b/voicechat_modem_dsp/modulators/modulator_psk.py index c6b4d8c..a5fd827 100644 --- a/voicechat_modem_dsp/modulators/modulator_psk.py +++ b/voicechat_modem_dsp/modulators/modulator_psk.py @@ -11,6 +11,8 @@ class PSKModulator(BaseModulator): def __init__(self, fs, carrier, amplitude, phase_list, baud): + if carrier<=0: + raise ValueError("Frequency of carrier must be positive") if baud>=0.5*carrier: raise ValueError("Baud is too high to be modulated "+ "using carrier frequency") diff --git a/voicechat_modem_dsp/modulators/modulator_qam.py b/voicechat_modem_dsp/modulators/modulator_qam.py index fe1a04b..3cc76f9 100644 --- a/voicechat_modem_dsp/modulators/modulator_qam.py +++ b/voicechat_modem_dsp/modulators/modulator_qam.py @@ -11,6 +11,8 @@ class QAMModulator(BaseModulator): def __init__(self, fs, carrier, constellation_list, baud): + if carrier<=0: + raise ValueError("Frequency of carrier must be positive") if baud>=0.5*carrier: raise ValueError("Baud is too high to be modulated "+ "using carrier frequency") From 8571b07c1aca68b6eae85ab87fd02c149d463ee4 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 May 2020 10:47:15 -0700 Subject: [PATCH 203/228] [ci skip] Update Pipfile.lock dependencies --- Pipfile.lock | 493 +++++++++++++++++++++++++-------------------------- 1 file changed, 240 insertions(+), 253 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index b51f71e..404f114 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -23,18 +23,26 @@ }, "cleo": { "hashes": [ - "sha256:9443d67e5b2da79b32d820ae41758dd6a25618345cb10b9a022a695e26b291b9", - "sha256:99cf342406f3499cec43270fcfaf93c126c5164092eca201dfef0f623360b409" + "sha256:141cda6dc94a92343be626bb87a0b6c86ae291dfc732a57bf04310d4b4201753", + "sha256:3d0e22d30117851b45970b6c14aca4ab0b18b1b53c8af57bed13208147e4069f" ], "index": "pypi", - "version": "==0.7.6" + "version": "==0.8.1" }, "clikit": { "hashes": [ - "sha256:95394982cfa460a77ded2f173380a958e5f90c16972307c19d79b96f6e335326", - "sha256:f67336462800078e0896cf6ecfa3b460dfea4dfa01de659388a4ff0d83c8d6ca" + "sha256:16b8aa2703413eae1138b50bdf825cb1465e0ee456efab4e8effe3bf03b7c2a4", + "sha256:a666d0727b1ddbfdef9ed2bfbda00ad6f1dd1fa19255ff79b8ca05df77809b7b" ], - "version": "==0.4.2" + "version": "==0.6.0" + }, + "crashtest": { + "hashes": [ + "sha256:06069a9267c54be31c42b03574b72407bf780e13c82cb0238f24ea69cf25b6dd", + "sha256:e9c06cc96400939ab5327123a3f699078eaad8a6283247d7b2ae0f6afffadf14" + ], + "markers": "python_version >= '3.6' and python_version < '4.0'", + "version": "==0.3.0" }, "cycler": { "hashes": [ @@ -52,18 +60,18 @@ }, "ipykernel": { "hashes": [ - "sha256:7f1f01df22f1229c8879501057877ccaf92a3b01c1d00db708aad5003e5f9238", - "sha256:ba8c9e5561f3223fb47ce06ad7925cb9444337ac367341c0c520ffb68ea6d120" + "sha256:003c9c1ab6ff87d11f531fee2b9ca59affab19676fc6b2c21da329aef6e73499", + "sha256:2937373c356fa5b634edb175c5ea0e4b25de8008f7c194f2d49cfbd1f9c970a8" ], "index": "pypi", - "version": "==5.1.4" + "version": "==5.2.1" }, "ipython": { "hashes": [ - "sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a", - "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333" + "sha256:5b241b84bbf0eb085d43ae9d46adf38a13b45929ca7774a740990c2c242534bb", + "sha256:f0126781d0f959da852fb3089e170ed807388e986a8dd4e6ac44855845b0fb1c" ], - "version": "==7.13.0" + "version": "==7.14.0" }, "ipython-genutils": { "hashes": [ @@ -74,17 +82,17 @@ }, "jedi": { "hashes": [ - "sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2", - "sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5" + "sha256:cd60c93b71944d628ccac47df9a60fec53150de53d42dc10a7fc4b5ba6aae798", + "sha256:df40c97641cb943661d2db4c33c2e1ff75d491189423249e989bcea4464f3030" ], - "version": "==0.16.0" + "version": "==0.17.0" }, "jupyter-client": { "hashes": [ - "sha256:1fac6e3be1e797aea33d5cd1cfa568ff1ee71e01180bc89f64b24ee274f1f126", - "sha256:ed2490c65f7e0987d1e7b2c4146371d58112489e558b3a835aefb86b7283f930" + "sha256:3a32fa4d0b16d1c626b30c3002a62dfd86d6863ed39eaba3f537fade197bb756", + "sha256:cde8e83aab3ec1c614f221ae54713a9a46d3bf28292609d2db1b439bef5a8c8e" ], - "version": "==6.0.0" + "version": "==6.1.3" }, "jupyter-core": { "hashes": [ @@ -95,99 +103,78 @@ }, "kiwisolver": { "hashes": [ - "sha256:05b5b061e09f60f56244adc885c4a7867da25ca387376b02c1efc29cc16bcd0f", - "sha256:210d8c39d01758d76c2b9a693567e1657ec661229bc32eac30761fa79b2474b0", - "sha256:26f4fbd6f5e1dabff70a9ba0d2c4bd30761086454aa30dddc5b52764ee4852b7", - "sha256:3b15d56a9cd40c52d7ab763ff0bc700edbb4e1a298dc43715ecccd605002cf11", - "sha256:3b2378ad387f49cbb328205bda569b9f87288d6bc1bf4cd683c34523a2341efe", - "sha256:400599c0fe58d21522cae0e8b22318e09d9729451b17ee61ba8e1e7c0346565c", - "sha256:47b8cb81a7d18dbaf4fed6a61c3cecdb5adec7b4ac292bddb0d016d57e8507d5", - "sha256:53eaed412477c836e1b9522c19858a8557d6e595077830146182225613b11a75", - "sha256:58e626e1f7dfbb620d08d457325a4cdac65d1809680009f46bf41eaf74ad0187", - "sha256:5a52e1b006bfa5be04fe4debbcdd2688432a9af4b207a3f429c74ad625022641", - "sha256:5c7ca4e449ac9f99b3b9d4693debb1d6d237d1542dd6a56b3305fe8a9620f883", - "sha256:682e54f0ce8f45981878756d7203fd01e188cc6c8b2c5e2cf03675390b4534d5", - "sha256:76275ee077772c8dde04fb6c5bc24b91af1bb3e7f4816fd1852f1495a64dad93", - "sha256:79bfb2f0bd7cbf9ea256612c9523367e5ec51d7cd616ae20ca2c90f575d839a2", - "sha256:7f4dd50874177d2bb060d74769210f3bce1af87a8c7cf5b37d032ebf94f0aca3", - "sha256:8944a16020c07b682df861207b7e0efcd2f46c7488619cb55f65882279119389", - "sha256:8aa7009437640beb2768bfd06da049bad0df85f47ff18426261acecd1cf00897", - "sha256:9105ce82dcc32c73eb53a04c869b6a4bc756b43e4385f76ea7943e827f529e4d", - "sha256:933df612c453928f1c6faa9236161a1d999a26cd40abf1dc5d7ebbc6dbfb8fca", - "sha256:939f36f21a8c571686eb491acfffa9c7f1ac345087281b412d63ea39ca14ec4a", - "sha256:9491578147849b93e70d7c1d23cb1229458f71fc79c51d52dce0809b2ca44eea", - "sha256:9733b7f64bd9f807832d673355f79703f81f0b3e52bfce420fc00d8cb28c6a6c", - "sha256:a02f6c3e229d0b7220bd74600e9351e18bc0c361b05f29adae0d10599ae0e326", - "sha256:a0c0a9f06872330d0dd31b45607197caab3c22777600e88031bfe66799e70bb0", - "sha256:aa716b9122307c50686356cfb47bfbc66541868078d0c801341df31dca1232a9", - "sha256:acc4df99308111585121db217681f1ce0eecb48d3a828a2f9bbf9773f4937e9e", - "sha256:b64916959e4ae0ac78af7c3e8cef4becee0c0e9694ad477b4c6b3a536de6a544", - "sha256:d22702cadb86b6fcba0e6b907d9f84a312db9cd6934ee728144ce3018e715ee1", - "sha256:d3fcf0819dc3fea58be1fd1ca390851bdb719a549850e708ed858503ff25d995", - "sha256:d52e3b1868a4e8fd18b5cb15055c76820df514e26aa84cc02f593d99fef6707f", - "sha256:db1a5d3cc4ae943d674718d6c47d2d82488ddd94b93b9e12d24aabdbfe48caee", - "sha256:e3a21a720791712ed721c7b95d433e036134de6f18c77dbe96119eaf7aa08004", - "sha256:e8bf074363ce2babeb4764d94f8e65efd22e6a7c74860a4f05a6947afc020ff2", - "sha256:f16814a4a96dc04bf1da7d53ee8d5b1d6decfc1a92a63349bb15d37b6a263dd9", - "sha256:f2b22153870ca5cf2ab9c940d7bc38e8e9089fa0f7e5856ea195e1cf4ff43d5a", - "sha256:f790f8b3dff3d53453de6a7b7ddd173d2e020fb160baff578d578065b108a05f", - "sha256:fe51b79da0062f8e9d49ed0182a626a7dc7a0cbca0328f612c6ee5e4711c81e4" - ], - "version": "==1.1.0" + "sha256:03662cbd3e6729f341a97dd2690b271e51a67a68322affab12a5b011344b973c", + "sha256:18d749f3e56c0480dccd1714230da0f328e6e4accf188dd4e6884bdd06bf02dd", + "sha256:247800260cd38160c362d211dcaf4ed0f7816afb5efe56544748b21d6ad6d17f", + "sha256:443c2320520eda0a5b930b2725b26f6175ca4453c61f739fef7a5847bd262f74", + "sha256:4eadb361baf3069f278b055e3bb53fa189cea2fd02cb2c353b7a99ebb4477ef1", + "sha256:556da0a5f60f6486ec4969abbc1dd83cf9b5c2deadc8288508e55c0f5f87d29c", + "sha256:603162139684ee56bcd57acc74035fceed7dd8d732f38c0959c8bd157f913fec", + "sha256:60a78858580761fe611d22127868f3dc9f98871e6fdf0a15cc4203ed9ba6179b", + "sha256:7cc095a4661bdd8a5742aaf7c10ea9fac142d76ff1770a0f84394038126d8fc7", + "sha256:c31bc3c8e903d60a1ea31a754c72559398d91b5929fcb329b1c3a3d3f6e72113", + "sha256:c955791d80e464da3b471ab41eb65cf5a40c15ce9b001fdc5bbc241170de58ec", + "sha256:d069ef4b20b1e6b19f790d00097a5d5d2c50871b66d10075dab78938dc2ee2cf", + "sha256:d52b989dc23cdaa92582ceb4af8d5bcc94d74b2c3e64cd6785558ec6a879793e", + "sha256:e586b28354d7b6584d8973656a7954b1c69c93f708c0c07b77884f91640b7657", + "sha256:efcf3397ae1e3c3a4a0a0636542bcad5adad3b1dd3e8e629d0b6e201347176c8", + "sha256:fccefc0d36a38c57b7bd233a9b485e2f1eb71903ca7ad7adacad6c28a56d62d2" + ], + "version": "==1.2.0" }, "matplotlib": { "hashes": [ - "sha256:0711b07920919951b2c508a773c433cbe07bdad952ea84ed9d18ca7853ccbe8b", - "sha256:0ab307e610302971012dc2387c97fc68e58c8eb00045a2c735da1b16353a3e3f", - "sha256:651d76daf9168250370d4befb09f79875daa2224a9096d97dfc3ed764c842be4", - "sha256:8e931015769322ee6860cabb8f975f628788e851092fd5edbdb065b5a516e3af", - "sha256:97a03e73f9ab71db8e4084894550c3af420c8ab1989b5e1306261b17576bf61b", - "sha256:9d174cc9681184023a7d520079eb0c085208761c6562710c1de7263d08217ab6", - "sha256:b21479a4478070c1c0f460e1bf1b65341e6a70ae0da905fcee836651450c66bb", - "sha256:b93377c6720e7db9cbba57e856a21aae2ff707677a6ee6b3b9d485f22ed82697", - "sha256:be937f34047bc09ed22d6a19d970fdc61d5d3191aa62f3262fc7f308e6d2e7f9", - "sha256:d281862a68b0bfce8f9e02a8e5acaa5cfbec37f37320f59b52eaf54b6423ec13", - "sha256:d5287cfcabad6f0f71a2627c1bbb6fb0cddacb9844f6c91f210604faa508f562", - "sha256:d75f5e952562f5e494ae92c1f917fc96c2ce09305a7c1bdc2e6502d3c61fbdc3", - "sha256:ee8acb1d4ee204e5cfe361d8f00d7e52c68f81c099b6c6048a3c76bf2c6b46e6", - "sha256:fc84f7c7cf1c5a9dbceadb7546818228f019d3b113ce5e362120c895fbba2944" + "sha256:2466d4dddeb0f5666fd1e6736cc5287a4f9f7ae6c1a9e0779deff798b28e1d35", + "sha256:282b3fc8023c4365bad924d1bb442ddc565c2d1635f210b700722776da466ca3", + "sha256:4bb50ee4755271a2017b070984bcb788d483a8ce3132fab68393d1555b62d4ba", + "sha256:56d3147714da5c7ac4bc452d041e70e0e0b07c763f604110bd4e2527f320b86d", + "sha256:7a9baefad265907c6f0b037c8c35a10cf437f7708c27415a5513cf09ac6d6ddd", + "sha256:aae7d107dc37b4bb72dcc45f70394e6df2e5e92ac4079761aacd0e2ad1d3b1f7", + "sha256:af14e77829c5b5d5be11858d042d6f2459878f8e296228c7ea13ec1fd308eb68", + "sha256:c1cf735970b7cd424502719b44288b21089863aaaab099f55e0283a721aaf781", + "sha256:ce378047902b7a05546b6485b14df77b2ff207a0054e60c10b5680132090c8ee", + "sha256:d35891a86a4388b6965c2d527b9a9f9e657d9e110b0575ca8a24ba0d4e34b8fc", + "sha256:e06304686209331f99640642dee08781a9d55c6e32abb45ed54f021f46ccae47", + "sha256:e20ba7fb37d4647ac38f3c6d8672dd8b62451ee16173a0711b37ba0ce42bf37d", + "sha256:f4412241e32d0f8d3713b68d3ca6430190a5e8a7c070f1c07d7833d8c5264398", + "sha256:ffe2f9cdcea1086fc414e82f42271ecf1976700b8edd16ca9d376189c6d93aee" ], "index": "pypi", - "version": "==3.2.0" + "version": "==3.2.1" }, "numpy": { "hashes": [ - "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", - "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", - "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", - "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", - "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", - "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", - "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", - "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", - "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", - "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", - "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", - "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", - "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", - "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", - "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", - "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", - "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", - "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", - "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", - "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", - "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5" + "sha256:00d7b54c025601e28f468953d065b9b121ddca7fff30bed7be082d3656dd798d", + "sha256:02ec9582808c4e48be4e93cd629c855e644882faf704bc2bd6bbf58c08a2a897", + "sha256:0e6f72f7bb08f2f350ed4408bb7acdc0daba637e73bce9f5ea2b207039f3af88", + "sha256:1be2e96314a66f5f1ce7764274327fd4fb9da58584eaff00b5a5221edefee7d6", + "sha256:2466fbcf23711ebc5daa61d28ced319a6159b260a18839993d871096d66b93f7", + "sha256:2b573fcf6f9863ce746e4ad00ac18a948978bb3781cffa4305134d31801f3e26", + "sha256:3f0dae97e1126f529ebb66f3c63514a0f72a177b90d56e4bce8a0b5def34627a", + "sha256:50fb72bcbc2cf11e066579cb53c4ca8ac0227abb512b6cbc1faa02d1595a2a5d", + "sha256:57aea170fb23b1fd54fa537359d90d383d9bf5937ee54ae8045a723caa5e0961", + "sha256:709c2999b6bd36cdaf85cf888d8512da7433529f14a3689d6e37ab5242e7add5", + "sha256:7d59f21e43bbfd9a10953a7e26b35b6849d888fc5a331fa84a2d9c37bd9fe2a2", + "sha256:904b513ab8fbcbdb062bed1ce2f794ab20208a1b01ce9bd90776c6c7e7257032", + "sha256:96dd36f5cdde152fd6977d1bbc0f0561bccffecfde63cd397c8e6033eb66baba", + "sha256:9933b81fecbe935e6a7dc89cbd2b99fea1bf362f2790daf9422a7bb1dc3c3085", + "sha256:bbcc85aaf4cd84ba057decaead058f43191cc0e30d6bc5d44fe336dc3d3f4509", + "sha256:dccd380d8e025c867ddcb2f84b439722cf1f23f3a319381eac45fd077dee7170", + "sha256:e22cd0f72fc931d6abc69dc7764484ee20c6a60b0d0fee9ce0426029b1c1bdae", + "sha256:ed722aefb0ebffd10b32e67f48e8ac4c5c4cf5d3a785024fdf0e9eb17529cd9d", + "sha256:efb7ac5572c9a57159cf92c508aad9f856f1cb8e8302d7fdb99061dbe52d712c", + "sha256:efdba339fffb0e80fcc19524e4fdbda2e2b5772ea46720c44eaac28096d60720", + "sha256:f22273dd6a403ed870207b853a856ff6327d5cbce7a835dfa0645b3fc00273ec" ], "index": "pypi", - "version": "==1.18.1" + "version": "==1.18.4" }, "parso": { "hashes": [ - "sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157", - "sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995" + "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0", + "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c" ], - "version": "==0.6.2" + "version": "==0.7.0" }, "pastel": { "hashes": [ @@ -213,10 +200,10 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e", - "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a" + "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8", + "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04" ], - "version": "==3.0.3" + "version": "==3.0.5" }, "protobuf": { "hashes": [ @@ -252,10 +239,10 @@ }, "pygments": { "hashes": [ - "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", - "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" + "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", + "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" ], - "version": "==2.5.2" + "version": "==2.6.1" }, "pylev": { "hashes": [ @@ -266,10 +253,10 @@ }, "pyparsing": { "hashes": [ - "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", - "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "version": "==2.4.6" + "version": "==2.4.7" }, "python-dateutil": { "hashes": [ @@ -280,36 +267,36 @@ }, "pyzmq": { "hashes": [ - "sha256:0bbc1728fe4314b4ca46249c33873a390559edac7c217ec7001b5e0c34a8fb7f", - "sha256:1e076ad5bd3638a18c376544d32e0af986ca10d43d4ce5a5d889a8649f0d0a3d", - "sha256:242d949eb6b10197cda1d1cec377deab1d5324983d77e0d0bf9dc5eb6d71a6b4", - "sha256:26f4ae420977d2a8792d7c2d7bda43128b037b5eeb21c81951a94054ad8b8843", - "sha256:32234c21c5e0a767c754181c8112092b3ddd2e2a36c3f76fc231ced817aeee47", - "sha256:3f12ce1e9cc9c31497bd82b207e8e86ccda9eebd8c9f95053aae46d15ccd2196", - "sha256:4557d5e036e6d85715b4b9fdb482081398da1d43dc580d03db642b91605b409f", - "sha256:4f562dab21c03c7aa061f63b147a595dbe1006bf4f03213272fc9f7d5baec791", - "sha256:5e071b834051e9ecb224915398f474bfad802c2fff883f118ff5363ca4ae3edf", - "sha256:5e1f65e576ab07aed83f444e201d86deb01cd27dcf3f37c727bc8729246a60a8", - "sha256:5f10a31f288bf055be76c57710807a8f0efdb2b82be6c2a2b8f9a61f33a40cea", - "sha256:6aaaf90b420dc40d9a0e1996b82c6a0ff91d9680bebe2135e67c9e6d197c0a53", - "sha256:75238d3c16cab96947705d5709187a49ebb844f54354cdf0814d195dd4c045de", - "sha256:7f7e7b24b1d392bb5947ba91c981e7d1a43293113642e0d8870706c8e70cdc71", - "sha256:84b91153102c4bcf5d0f57d1a66a0f03c31e9e6525a5f656f52fc615a675c748", - "sha256:944f6bb5c63140d76494467444fd92bebd8674236837480a3c75b01fe17df1ab", - "sha256:a1f957c20c9f51d43903881399b078cddcf710d34a2950e88bce4e494dcaa4d1", - "sha256:a49fd42a29c1cc1aa9f461c5f2f5e0303adba7c945138b35ee7f4ab675b9f754", - "sha256:a99ae601b4f6917985e9bb071549e30b6f93c72f5060853e197bdc4b7d357e5f", - "sha256:ad48865a29efa8a0cecf266432ea7bc34e319954e55cf104be0319c177e6c8f5", - "sha256:b08e425cf93b4e018ab21dc8fdbc25d7d0502a23cc4fea2380010cf8cf11e462", - "sha256:bb10361293d96aa92be6261fa4d15476bca56203b3a11c62c61bd14df0ef89ba", - "sha256:bd1a769d65257a7a12e2613070ca8155ee348aa9183f2aadf1c8b8552a5510f5", - "sha256:cb3b7156ef6b1a119e68fbe3a54e0a0c40ecacc6b7838d57dd708c90b62a06dc", - "sha256:e8e4efb52ec2df8d046395ca4c84ae0056cf507b2f713ec803c65a8102d010de", - "sha256:f37c29da2a5b0c5e31e6f8aab885625ea76c807082f70b2d334d3fd573c3100a", - "sha256:f4d558bc5668d2345773a9ff8c39e2462dafcb1f6772a2e582fbced389ce527f", - "sha256:f5b6d015587a1d6f582ba03b226a9ddb1dfb09878b3be04ef48b01b7d4eb6b2a" - ], - "version": "==19.0.0" + "sha256:07fb8fe6826a229dada876956590135871de60dbc7de5a18c3bcce2ed1f03c98", + "sha256:13a5638ab24d628a6ade8f794195e1a1acd573496c3b85af2f1183603b7bf5e0", + "sha256:15b4cb21118f4589c4db8be4ac12b21c8b4d0d42b3ee435d47f686c32fe2e91f", + "sha256:21f7d91f3536f480cb2c10d0756bfa717927090b7fb863e6323f766e5461ee1c", + "sha256:2a88b8fabd9cc35bd59194a7723f3122166811ece8b74018147a4ed8489e6421", + "sha256:342fb8a1dddc569bc361387782e8088071593e7eaf3e3ecf7d6bd4976edff112", + "sha256:4ee0bfd82077a3ff11c985369529b12853a4064320523f8e5079b630f9551448", + "sha256:54aa24fd60c4262286fc64ca632f9e747c7cc3a3a1144827490e1dc9b8a3a960", + "sha256:58688a2dfa044fad608a8e70ba8d019d0b872ec2acd75b7b5e37da8905605891", + "sha256:5b99c2ae8089ef50223c28bac57510c163bfdff158c9e90764f812b94e69a0e6", + "sha256:5b9d21fc56c8aacd2e6d14738021a9d64f3f69b30578a99325a728e38a349f85", + "sha256:5f1f2eb22aab606f808163eb1d537ac9a0ba4283fbeb7a62eb48d9103cf015c2", + "sha256:6ca519309703e95d55965735a667809bbb65f52beda2fdb6312385d3e7a6d234", + "sha256:87c78f6936e2654397ca2979c1d323ee4a889eef536cc77a938c6b5be33351a7", + "sha256:8952f6ba6ae598e792703f3134af5a01af8f5c7cf07e9a148f05a12b02412cea", + "sha256:931339ac2000d12fe212e64f98ce291e81a7ec6c73b125f17cf08415b753c087", + "sha256:956775444d01331c7eb412c5fb9bb62130dfaac77e09f32764ea1865234e2ca9", + "sha256:97b6255ae77328d0e80593681826a0479cb7bac0ba8251b4dd882f5145a2293a", + "sha256:aaa8b40b676576fd7806839a5de8e6d5d1b74981e6376d862af6c117af2a3c10", + "sha256:af0c02cf49f4f9eedf38edb4f3b6bb621d83026e7e5d76eb5526cc5333782fd6", + "sha256:b08780e3a55215873b3b8e6e7ca8987f14c902a24b6ac081b344fd430d6ca7cd", + "sha256:ba6f24431b569aec674ede49cad197cad59571c12deed6ad8e3c596da8288217", + "sha256:bafd651b557dd81d89bd5f9c678872f3e7b7255c1c751b78d520df2caac80230", + "sha256:bfff5ffff051f5aa47ba3b379d87bd051c3196b0c8a603e8b7ed68a6b4f217ec", + "sha256:cf5d689ba9513b9753959164cf500079383bc18859f58bf8ce06d8d4bef2b054", + "sha256:dcbc3f30c11c60d709c30a213dc56e88ac016fe76ac6768e64717bd976072566", + "sha256:f9d7e742fb0196992477415bb34366c12e9bb9a0699b8b3f221ff93b213d7bec", + "sha256:faee2604f279d31312bc455f3d024f160b6168b9c1dde22bf62d8c88a4deca8e" + ], + "version": "==19.0.1" }, "ruamel.yaml": { "hashes": [ @@ -407,10 +394,10 @@ }, "wcwidth": { "hashes": [ - "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603", - "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8" + "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", + "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" ], - "version": "==0.1.8" + "version": "==0.1.9" } }, "develop": { @@ -437,17 +424,17 @@ }, "bleach": { "hashes": [ - "sha256:44f69771e2ac81ff30d929d485b7f9919f3ad6d019b6b20c74f3b8687c3f70df", - "sha256:aa8b870d0f46965bac2c073a93444636b0e1ca74e9777e34f03dd494b8a59d48" + "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f", + "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b" ], - "version": "==3.1.1" + "version": "==3.1.5" }, "certifi": { "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", + "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" ], - "version": "==2019.11.28" + "version": "==2020.4.5.1" }, "chardet": { "hashes": [ @@ -458,40 +445,40 @@ }, "coverage": { "hashes": [ - "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3", - "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c", - "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0", - "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477", - "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a", - "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf", - "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691", - "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73", - "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987", - "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894", - "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e", - "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef", - "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf", - "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68", - "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8", - "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954", - "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2", - "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40", - "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc", - "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc", - "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e", - "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d", - "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f", - "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc", - "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301", - "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea", - "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb", - "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af", - "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52", - "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37", - "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0" + "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a", + "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355", + "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65", + "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7", + "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9", + "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1", + "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0", + "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55", + "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c", + "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6", + "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef", + "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019", + "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e", + "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0", + "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf", + "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24", + "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2", + "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c", + "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4", + "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0", + "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd", + "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04", + "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e", + "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730", + "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2", + "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768", + "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796", + "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7", + "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a", + "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489", + "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052" ], "index": "pypi", - "version": "==5.0.3" + "version": "==5.1" }, "decorator": { "hashes": [ @@ -537,11 +524,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", - "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" + "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", + "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" ], "markers": "python_version < '3.8'", - "version": "==1.5.0" + "version": "==1.6.0" }, "ipython-genutils": { "hashes": [ @@ -560,10 +547,10 @@ }, "jinja2": { "hashes": [ - "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", - "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" + "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", + "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" ], - "version": "==2.11.1" + "version": "==2.11.2" }, "jsonschema": { "hashes": [ @@ -633,24 +620,24 @@ }, "mypy": { "hashes": [ - "sha256:0a9a45157e532da06fe56adcfef8a74629566b607fa2c1ac0122d1ff995c748a", - "sha256:2c35cae79ceb20d47facfad51f952df16c2ae9f45db6cb38405a3da1cf8fc0a7", - "sha256:4b9365ade157794cef9685791032521233729cb00ce76b0ddc78749abea463d2", - "sha256:53ea810ae3f83f9c9b452582261ea859828a9ed666f2e1ca840300b69322c474", - "sha256:634aef60b4ff0f650d3e59d4374626ca6153fcaff96ec075b215b568e6ee3cb0", - "sha256:7e396ce53cacd5596ff6d191b47ab0ea18f8e0ec04e15d69728d530e86d4c217", - "sha256:7eadc91af8270455e0d73565b8964da1642fe226665dd5c9560067cd64d56749", - "sha256:7f672d02fffcbace4db2b05369142e0506cdcde20cea0e07c7c2171c4fd11dd6", - "sha256:85baab8d74ec601e86134afe2bcccd87820f79d2f8d5798c889507d1088287bf", - "sha256:87c556fb85d709dacd4b4cb6167eecc5bbb4f0a9864b69136a0d4640fdc76a36", - "sha256:a6bd44efee4dc8c3324c13785a9dc3519b3ee3a92cada42d2b57762b7053b49b", - "sha256:c6d27bd20c3ba60d5b02f20bd28e20091d6286a699174dfad515636cb09b5a72", - "sha256:e2bb577d10d09a2d8822a042a23b8d62bc3b269667c9eb8e60a6edfa000211b1", - "sha256:f97a605d7c8bc2c6d1172c2f0d5a65b24142e11a58de689046e62c2d632ca8c1" + "sha256:15b948e1302682e3682f11f50208b726a246ab4e6c1b39f9264a8796bb416aa2", + "sha256:219a3116ecd015f8dca7b5d2c366c973509dfb9a8fc97ef044a36e3da66144a1", + "sha256:3b1fc683fb204c6b4403a1ef23f0b1fac8e4477091585e0c8c54cbdf7d7bb164", + "sha256:3beff56b453b6ef94ecb2996bea101a08f1f8a9771d3cbf4988a61e4d9973761", + "sha256:7687f6455ec3ed7649d1ae574136835a4272b65b3ddcf01ab8704ac65616c5ce", + "sha256:7ec45a70d40ede1ec7ad7f95b3c94c9cf4c186a32f6bacb1795b60abd2f9ef27", + "sha256:86c857510a9b7c3104cf4cde1568f4921762c8f9842e987bc03ed4f160925754", + "sha256:8a627507ef9b307b46a1fea9513d5c98680ba09591253082b4c48697ba05a4ae", + "sha256:8dfb69fbf9f3aeed18afffb15e319ca7f8da9642336348ddd6cab2713ddcf8f9", + "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600", + "sha256:a8ffcd53cb5dfc131850851cc09f1c44689c2812d0beb954d8138d4f5fc17f65", + "sha256:b90928f2d9eb2f33162405f32dde9f6dcead63a0971ca8a1b50eb4ca3e35ceb8", + "sha256:c56ffe22faa2e51054c5f7a3bc70a370939c2ed4de308c690e7949230c995913", + "sha256:f91c7ae919bbc3f96cd5e5b2e786b2b108343d1d7972ea130f7de27fdd547cf3" ], "index": "pypi", "markers": "python_version >= '3.5'", - "version": "==0.761" + "version": "==0.770" }, "mypy-extensions": { "hashes": [ @@ -668,45 +655,45 @@ }, "nbformat": { "hashes": [ - "sha256:562de41fc7f4f481b79ab5d683279bf3a168858268d4387b489b7b02be0b324a", - "sha256:f4bbbd8089bd346488f00af4ce2efb7f8310a74b2058040d075895429924678c" + "sha256:049af048ed76b95c3c44043620c17e56bc001329e07f83fec4f177f0e3d7b757", + "sha256:276343c78a9660ab2a63c28cc33da5f7c58c092b3f3a40b6017ae2ce6689320d" ], - "version": "==5.0.4" + "version": "==5.0.6" }, "nbsphinx": { "hashes": [ - "sha256:1c3e1b5c5b4c96c06c2e428536b6a7860ee23bc290dc4e6e73ee6ed5076a004e", - "sha256:e100472db293dcc2f7aae04ebebec0bfbe8eba88e209d04f367373fdd4b1455d" + "sha256:64bca3150cd9689e640980caf6e576a218e650419ffeca4afc09d6e31920d099", + "sha256:77545508fff12fed427fffbd9eae932712fe3db7cc6729b0af5bbd122d7146cf" ], "index": "pypi", - "version": "==0.5.1" + "version": "==0.7.0" }, "numpy": { "hashes": [ - "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", - "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", - "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", - "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", - "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", - "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", - "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", - "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", - "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", - "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", - "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", - "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", - "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", - "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", - "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", - "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", - "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", - "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", - "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", - "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", - "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5" + "sha256:00d7b54c025601e28f468953d065b9b121ddca7fff30bed7be082d3656dd798d", + "sha256:02ec9582808c4e48be4e93cd629c855e644882faf704bc2bd6bbf58c08a2a897", + "sha256:0e6f72f7bb08f2f350ed4408bb7acdc0daba637e73bce9f5ea2b207039f3af88", + "sha256:1be2e96314a66f5f1ce7764274327fd4fb9da58584eaff00b5a5221edefee7d6", + "sha256:2466fbcf23711ebc5daa61d28ced319a6159b260a18839993d871096d66b93f7", + "sha256:2b573fcf6f9863ce746e4ad00ac18a948978bb3781cffa4305134d31801f3e26", + "sha256:3f0dae97e1126f529ebb66f3c63514a0f72a177b90d56e4bce8a0b5def34627a", + "sha256:50fb72bcbc2cf11e066579cb53c4ca8ac0227abb512b6cbc1faa02d1595a2a5d", + "sha256:57aea170fb23b1fd54fa537359d90d383d9bf5937ee54ae8045a723caa5e0961", + "sha256:709c2999b6bd36cdaf85cf888d8512da7433529f14a3689d6e37ab5242e7add5", + "sha256:7d59f21e43bbfd9a10953a7e26b35b6849d888fc5a331fa84a2d9c37bd9fe2a2", + "sha256:904b513ab8fbcbdb062bed1ce2f794ab20208a1b01ce9bd90776c6c7e7257032", + "sha256:96dd36f5cdde152fd6977d1bbc0f0561bccffecfde63cd397c8e6033eb66baba", + "sha256:9933b81fecbe935e6a7dc89cbd2b99fea1bf362f2790daf9422a7bb1dc3c3085", + "sha256:bbcc85aaf4cd84ba057decaead058f43191cc0e30d6bc5d44fe336dc3d3f4509", + "sha256:dccd380d8e025c867ddcb2f84b439722cf1f23f3a319381eac45fd077dee7170", + "sha256:e22cd0f72fc931d6abc69dc7764484ee20c6a60b0d0fee9ce0426029b1c1bdae", + "sha256:ed722aefb0ebffd10b32e67f48e8ac4c5c4cf5d3a785024fdf0e9eb17529cd9d", + "sha256:efb7ac5572c9a57159cf92c508aad9f856f1cb8e8302d7fdb99061dbe52d712c", + "sha256:efdba339fffb0e80fcc19524e4fdbda2e2b5772ea46720c44eaac28096d60720", + "sha256:f22273dd6a403ed870207b853a856ff6327d5cbce7a835dfa0645b3fc00273ec" ], "index": "pypi", - "version": "==1.18.1" + "version": "==1.18.4" }, "numpy-stubs": { "editable": true, @@ -741,38 +728,38 @@ }, "pygments": { "hashes": [ - "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", - "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" + "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", + "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" ], - "version": "==2.5.2" + "version": "==2.6.1" }, "pyparsing": { "hashes": [ - "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", - "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "version": "==2.4.6" + "version": "==2.4.7" }, "pyrsistent": { "hashes": [ - "sha256:cdc7b5e3ed77bed61270a47d35434a30617b9becdf2478af76ad2c6ade307280" + "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3" ], - "version": "==0.15.7" + "version": "==0.16.0" }, "pytest": { "hashes": [ - "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d", - "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6" + "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3", + "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698" ], "index": "pypi", - "version": "==5.3.5" + "version": "==5.4.2" }, "pytz": { "hashes": [ - "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", - "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" + "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", + "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" ], - "version": "==2019.3" + "version": "==2020.1" }, "requests": { "hashes": [ @@ -797,11 +784,11 @@ }, "sphinx": { "hashes": [ - "sha256:b4c750d546ab6d7e05bdff6ac24db8ae3e8b8253a3569b754e445110a0a12b66", - "sha256:fc312670b56cb54920d6cc2ced455a22a547910de10b3142276495ced49231cb" + "sha256:62edfd92d955b868d6c124c0942eba966d54b5f3dcb4ded39e65f74abac3f572", + "sha256:f5505d74cf9592f3b997380f9bdb2d2d0320ed74dd69691e3ee0644b956b8d83" ], "index": "pypi", - "version": "==2.4.4" + "version": "==3.0.3" }, "sphinxcontrib-applehelp": { "hashes": [ @@ -887,25 +874,25 @@ }, "typing-extensions": { "hashes": [ - "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", - "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", - "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575" + "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5", + "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae", + "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392" ], - "version": "==3.7.4.1" + "version": "==3.7.4.2" }, "urllib3": { "hashes": [ - "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", - "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" ], - "version": "==1.25.8" + "version": "==1.25.9" }, "wcwidth": { "hashes": [ - "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603", - "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8" + "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", + "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" ], - "version": "==0.1.8" + "version": "==0.1.9" }, "webencodings": { "hashes": [ From d9b528140387533a3a33d6172d76b7dd832f15ee Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 May 2020 18:35:34 -0700 Subject: [PATCH 204/228] Extend demodulated signal with filt_delay padding Very lucky that my filter was small enough for me not to lose the end symbols... --- voicechat_modem_dsp/modulators/modulator_ask.py | 3 ++- voicechat_modem_dsp/modulators/modulator_psk.py | 5 +++-- voicechat_modem_dsp/modulators/modulator_qam.py | 11 ++++++----- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index a679939..1d960ce 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -97,7 +97,8 @@ def demodulate(self, modulated_data): # Construct FIR filter, filter demodulated signal, and discard phase fir_filt=modulator_utils.lowpass_fir_filter(self.fs, filter_lowend, filter_highend) filt_delay=(len(fir_filt)-1)//2 - + # First append filt_delay number of zeros to incoming signal + demod_signal=np.pad(demod_signal,(0,filt_delay)) filtered_demod_amplitude=np.abs(signal.lfilter(fir_filt,1,demod_amplitude)) # Extract the original amplitudes via averaging of plateau diff --git a/voicechat_modem_dsp/modulators/modulator_psk.py b/voicechat_modem_dsp/modulators/modulator_psk.py index a5fd827..e64cf51 100644 --- a/voicechat_modem_dsp/modulators/modulator_psk.py +++ b/voicechat_modem_dsp/modulators/modulator_psk.py @@ -109,10 +109,11 @@ def demodulate(self, modulated_data): if carrier_refl-4000>self.carrier_freq: filter_highend=carrier_refl-4000 - # Construct FIR filter, filter demodulated signal, and discard phase + # Construct FIR filter, filter demodulated signal fir_filt=modulator_utils.lowpass_fir_filter(self.fs, filter_lowend, filter_highend) filt_delay=(len(fir_filt)-1)//2 - + # First append filt_delay number of zeros to incoming signal + demod_signal=np.pad(demod_signal,(0,filt_delay)) filtered_demod_signal=signal.lfilter(fir_filt,1,demod_signal) # Extract the original amplitudes via averaging of plateau diff --git a/voicechat_modem_dsp/modulators/modulator_qam.py b/voicechat_modem_dsp/modulators/modulator_qam.py index 3cc76f9..0aa4a98 100644 --- a/voicechat_modem_dsp/modulators/modulator_qam.py +++ b/voicechat_modem_dsp/modulators/modulator_qam.py @@ -72,14 +72,14 @@ def modulate(self, datastream): constellation_data = np.pad([self.constellation_list[datum] for datum in datastream], 1,mode="constant",constant_values=0+0j) - # Upsample amplitude to actual sampling rate + # Upsample data to actual sampling rate interp_sample_count=int(np.ceil( len(constellation_data)*samples_per_symbol)) time_array=modulator_utils.generate_timearray( self.fs,interp_sample_count) interpolated_quad=modulator_utils.previous_resample_interpolate( time_array, self.baud, constellation_data) - # Smooth phases with Gaussian kernel after mapping to complex plane + # Smooth values with Gaussian kernel after mapping to complex plane shaped_quad=signal.convolve(interpolated_quad,gaussian_window, "same",method="fft") @@ -88,7 +88,7 @@ def modulate(self, datastream): np.exp(2*np.pi*1j*self.carrier_freq*time_array)) def demodulate(self, modulated_data): - # TODO: copy this over to the QAM modulator + # TODO: copy this over to the phase modulator # This is close enough to maybe allow object composition samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) time_array=modulator_utils.generate_timearray( @@ -105,10 +105,11 @@ def demodulate(self, modulated_data): if carrier_refl-4000>self.carrier_freq: filter_highend=carrier_refl-4000 - # Construct FIR filter, filter demodulated signal, and discard phase + # Construct FIR filter and filter demodulated signal fir_filt=modulator_utils.lowpass_fir_filter(self.fs, filter_lowend, filter_highend) filt_delay=(len(fir_filt)-1)//2 - + # First append filt_delay number of zeros to incoming signal + demod_signal=np.pad(demod_signal,(0,filt_delay)) filtered_demod_signal=signal.lfilter(fir_filt,1,demod_signal) # Extract the original amplitudes via averaging of plateau From 28534f7ceeeea8d8bfdef701f2f8cc96a99e0c50 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 May 2020 19:53:33 -0700 Subject: [PATCH 205/228] Fix signal name typo in ASK modulator --- voicechat_modem_dsp/modulators/modulator_ask.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 1d960ce..faa3c03 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -98,7 +98,7 @@ def demodulate(self, modulated_data): fir_filt=modulator_utils.lowpass_fir_filter(self.fs, filter_lowend, filter_highend) filt_delay=(len(fir_filt)-1)//2 # First append filt_delay number of zeros to incoming signal - demod_signal=np.pad(demod_signal,(0,filt_delay)) + demod_amplitude=np.pad(demod_amplitude,(0,filt_delay)) filtered_demod_amplitude=np.abs(signal.lfilter(fir_filt,1,demod_amplitude)) # Extract the original amplitudes via averaging of plateau From f9c14f59779b3084ec8b32456c73594766f2a0d6 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 May 2020 20:20:03 -0700 Subject: [PATCH 206/228] Add fs as an expected instance variable in the BaseModulator abstract class MyPy check_untyped_defs can now be enabled with this last issue out of the way --- mypy.ini | 2 +- voicechat_modem_dsp/modulators/modulator_base.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy.ini b/mypy.ini index ad22d82..a9ae3e2 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,5 +1,5 @@ [mypy] -#check_untyped_defs=True +check_untyped_defs=True [mypy-strictyaml] ignore_missing_imports=True diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index 2e661fd..a6bd97a 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -13,6 +13,7 @@ class BaseModulator(ABC): sigma_mult_t=2.89 # norm.isf(0.0001) sigma_mult_f=3.72 + fs: float @abstractmethod def modulate(self, datastream: Sequence[int]) -> ndarray: From 897292325bccdfdaa3be0234d3ad0e354e131dc8 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 May 2020 20:36:46 -0700 Subject: [PATCH 207/228] Turn PSKModulator into a thin wrapper around QAMModulator --- .../modulators/modulator_psk.py | 148 +++--------------- 1 file changed, 21 insertions(+), 127 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_psk.py b/voicechat_modem_dsp/modulators/modulator_psk.py index e64cf51..f5daffd 100644 --- a/voicechat_modem_dsp/modulators/modulator_psk.py +++ b/voicechat_modem_dsp/modulators/modulator_psk.py @@ -1,6 +1,7 @@ from .modulator_base import BaseModulator from . import modulator_utils +from .modulator_qam import QAMModulator from .modulator_utils import ModulationIntegrityWarning import numpy as np @@ -11,14 +12,16 @@ class PSKModulator(BaseModulator): def __init__(self, fs, carrier, amplitude, phase_list, baud): - if carrier<=0: - raise ValueError("Frequency of carrier must be positive") - if baud>=0.5*carrier: - raise ValueError("Baud is too high to be modulated "+ - "using carrier frequency") + # Checks commented out are already included in QAMModulator + + #if carrier<=0: + # raise ValueError("Frequency of carrier must be positive") + #if baud>=0.5*carrier: + # raise ValueError("Baud is too high to be modulated "+ + # "using carrier frequency") # Nyquist limit - if carrier>=0.5*fs: - raise ValueError("Carrier frequency is too high for sampling rate") + #if carrier>=0.5*fs: + # raise ValueError("Carrier frequency is too high for sampling rate") if any((x>=2*np.pi or x<0 for x in phase_list)): raise ValueError("Invalid phases given") @@ -30,11 +33,10 @@ def __init__(self, fs, carrier, amplitude, phase_list, baud): self.phase_list=dict(enumerate(phase_list)) self.carrier_freq=carrier self.baud=baud - # Nyquist aliased 2*carrier=carrier - if carrier>=(1/3)*fs: - warnings.warn("Carrier frequency is too high to guarantee " - "proper lowpass reconstruction", - ModulationIntegrityWarning) +# if carrier>=(1/3)*fs: +# warnings.warn("Carrier frequency is too high to guarantee " +# "proper lowpass reconstruction", +# ModulationIntegrityWarning) if amplitude<0.1: warnings.warn("Amplitude may be too small to allow " "reliable reconstruction",ModulationIntegrityWarning) @@ -42,124 +44,16 @@ def __init__(self, fs, carrier, amplitude, phase_list, baud): warnings.warn("Phases may be too close " "to be distinguishable from each other", ModulationIntegrityWarning) - # TODO: additional warnings relating to filter overshoot and the like - + constellation_points=amplitude*np.exp(1j*np.asarray(phase_list)) + self.qam_modulator=QAMModulator(self.fs,self.carrier_freq, + constellation_points,self.baud) + @property def _calculate_sigma(self): - #sigma_t = w/4k, at most half of the pulse is smoothed away - gaussian_sigma_t=(1/self.baud)/(4*BaseModulator.sigma_mult_t) - # Ensure dropoff at halfway to doubled carrier frequency is -80dB - # This is not the same as carrier_freq because Nyquist reflections - doubled_carrier_refl=min( - 2*self.carrier_freq,self.fs-2*self.carrier_freq) - halfway_thresh=0.5*doubled_carrier_refl - gaussian_sigma_f=BaseModulator.sigma_mult_f/(2*np.pi*halfway_thresh) - return min(gaussian_sigma_t,gaussian_sigma_f) + return self.qam_modulator._calculate_sigma() def modulate(self, datastream): - samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) - gaussian_sigma=self._calculate_sigma - gaussian_window=modulator_utils.gaussian_window(self.fs,gaussian_sigma) - - # Map datastream to phases and pad with 0 on both ends - phase_data = np.pad([self.phase_list[datum] for datum in datastream], - 1,mode="constant",constant_values=0) - - # Upsample amplitude to actual sampling rate - interp_sample_count=int(np.ceil( - len(phase_data)*samples_per_symbol)) - time_array=modulator_utils.generate_timearray( - self.fs,interp_sample_count) - interpolated_phase=modulator_utils.previous_resample_interpolate( - time_array, self.baud, phase_data) - # Smooth phases with Gaussian kernel after mapping to complex plane - interpolated_phase=np.exp(1j*interpolated_phase) - shaped_phase=signal.convolve(interpolated_phase,gaussian_window, - "same",method="fft") - - # Construct smoothed signal mask - # TODO: be smarter about only convolving the edges - amplitude_mask=np.asarray([0]+[self.amplitude]*len(datastream)+[0]) - interpolated_amplitude=modulator_utils.previous_resample_interpolate( - time_array,self.baud,amplitude_mask) - shaped_amplitude=signal.convolve(interpolated_amplitude,gaussian_window, - "same",method="fft") - - shaped_phase*=shaped_amplitude - - # Multiply amplitudes by carrier - return np.real(shaped_phase * - np.exp(2*np.pi*1j*self.carrier_freq*time_array)) + return self.qam_modulator.modulate(datastream) def demodulate(self, modulated_data): - # TODO: copy this over to the QAM modulator - # This is close enough to maybe allow object composition - samples_per_symbol=modulator_utils.samples_per_symbol(self.fs,self.baud) - time_array=modulator_utils.generate_timearray( - self.fs,len(modulated_data)) - demod_signal=2*modulated_data*np.exp(2*np.pi*1j*self.carrier_freq*time_array) - - # Compute filter boundaries - # Lowend is half the baud (i.e. the fundamental of the data) - # Highend blocks fundamental of data and optionally voice - # TODO: improve this part - carrier_refl=min(2*self.carrier_freq,self.fs-2*self.carrier_freq) - filter_lowend=0.5*self.baud - filter_highend=carrier_refl-filter_lowend - if carrier_refl-4000>self.carrier_freq: - filter_highend=carrier_refl-4000 - - # Construct FIR filter, filter demodulated signal - fir_filt=modulator_utils.lowpass_fir_filter(self.fs, filter_lowend, filter_highend) - filt_delay=(len(fir_filt)-1)//2 - # First append filt_delay number of zeros to incoming signal - demod_signal=np.pad(demod_signal,(0,filt_delay)) - filtered_demod_signal=signal.lfilter(fir_filt,1,demod_signal) - - # Extract the original amplitudes via averaging of plateau - - # Round to account for floating point weirdness - interval_count=int(np.round( - len(modulated_data)/samples_per_symbol)) - interval_offset=filt_delay - list_constellation=list() - - transition_width=BaseModulator.sigma_mult_t*self._calculate_sigma - # Convert above time width into sample point width - transition_width*=self.fs - - for i in range(interval_count): - interval_begin=interval_offset+i*samples_per_symbol - # Perform min in order to account for floating point weirdness - interval_end=min(interval_begin+samples_per_symbol, - len(modulated_data)-1) - - # Shrink interval by previously calculated transition width - # Skip doing so for first and last sample - if i!=0: - interval_begin+=transition_width - if i!=interval_count-1: - interval_end-=transition_width - # Find the amplitude by averaging - avg=modulator_utils.average_interval_data(filtered_demod_signal, - interval_begin, interval_end) - list_constellation.append(np.conj(avg)) - - # Convert observations and mapping into vq arguments - # Insert the null symbol 0 to account for beginning and end - list_constellation=[[np.real(point),np.imag(point)] for point in list_constellation] - code_book=[self.amplitude*np.exp(1j*self.phase_list[i]) - for i in range(len(self.phase_list))] - code_book.insert(0,0+0j) - code_book=[[np.real(obs),np.imag(obs)] for obs in code_book] - - # Map averages to amplitude points - vector_cluster=vq(list_constellation,code_book) - # Subtract data points by 1 and remove 0 padding - # Neat side effect: -1 is an invalid data point - datastream=vector_cluster[0]-1 - if (datastream[0]!=-1 or datastream[-1]!=-1 - or any(datastream[1:-1]==-1)): - warnings.warn("Corrupted datastream detected while demodulating", - ModulationIntegrityWarning) - return datastream[1:-1] + return self.qam_modulator.demodulate(modulated_data) From 339ea22fac142acdae1e77dce097bb20707ed0f6 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 May 2020 21:46:07 -0700 Subject: [PATCH 208/228] Adjust type hint to work with Python 3.5 --- voicechat_modem_dsp/modulators/modulator_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index a6bd97a..a360d53 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -13,7 +13,7 @@ class BaseModulator(ABC): sigma_mult_t=2.89 # norm.isf(0.0001) sigma_mult_f=3.72 - fs: float + fs=-1 #type: float @abstractmethod def modulate(self, datastream: Sequence[int]) -> ndarray: From 8d1dfab8966c0e574fbeead3d0d5d69feafa45f5 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Mon, 11 May 2020 21:59:06 -0700 Subject: [PATCH 209/228] Add tests that check for exception on negative carrier --- test/modulators/test_modulator_parameters.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/modulators/test_modulator_parameters.py b/test/modulators/test_modulator_parameters.py index 31d0486..f656825 100644 --- a/test/modulators/test_modulator_parameters.py +++ b/test/modulators/test_modulator_parameters.py @@ -34,7 +34,16 @@ def test_invalid_nyquist(): bad_modulator=PSKModulator(900,400,1,np.linspace(0,np.pi,8),100) @pytest.mark.unit -def test_invalid_modspecific(): +def test_invalid_analyticmodulator(): + with pytest.raises(ValueError, match=r"carrier.+positive"): + bad_modulator=ASKModulator(1000,-20,np.linspace(0.2,1,4),200) + with pytest.raises(ValueError, match=r"carrier.+positive"): + bad_modulator=PSKModulator(1000,-20,1,np.linspace(0,np.pi,4),200) + with pytest.raises(ValueError, match=r"carrier.+positive"): + bad_modulator=QAMModulator(1000,-20,np.linspace(0,1,8),200) + +@pytest.mark.unit +def test_invalid_askmodulator(): with pytest.raises(ValueError, match=r"Invalid amplitudes.+"): bad_modulator=ASKModulator(1000,200,np.linspace(-1,2,16),20) with pytest.warns(ModulationIntegrityWarning): @@ -42,6 +51,8 @@ def test_invalid_modspecific(): with pytest.warns(ModulationIntegrityWarning): bad_modulator=ASKModulator(2000,880,np.geomspace(0.2,1,256),40) +@pytest.mark.unit +def test_invalid_pskmodulator(): with pytest.raises(ValueError, match=r"Invalid phases.+"): bad_modulator=PSKModulator(1000,200,1,np.linspace(-1,10,16),20) with pytest.raises(ValueError, match=r"amplitude must be positive.+"): @@ -53,6 +64,8 @@ def test_invalid_modspecific(): with pytest.warns(ModulationIntegrityWarning): bad_modulator=PSKModulator(1000,200,0.02,np.linspace(0,0.1,16),50) +@pytest.mark.unit +def test_invalid_qammodulator(): with pytest.raises(ValueError, match=r"of constellation points.+"): bad_modulator=QAMModulator(1000,200,[2,4,-2j,1+1j],50) with pytest.warns(ModulationIntegrityWarning): @@ -60,6 +73,8 @@ def test_invalid_modspecific(): with pytest.warns(ModulationIntegrityWarning): bad_modulator=QAMModulator(2000,880,[0.05,1j],80) +@pytest.mark.unit +def test_invalid_fskmodulator(): with pytest.raises(ValueError, match=r"Frequencies.+positive.?"): bad_modulator=FSKModulator(1000,1,np.linspace(-1,1000,16),500) with pytest.raises(ValueError, match=r"amplitude must be positive.+"): From 1efc510b5f6581817bf5b71060b07a5040d28b61 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 12 May 2020 13:41:24 -0700 Subject: [PATCH 210/228] Update config script to state that QAM is now supported --- docs/specs/config.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/specs/config.rst b/docs/specs/config.rst index 094fb6f..7b17d16 100644 --- a/docs/specs/config.rst +++ b/docs/specs/config.rst @@ -30,9 +30,6 @@ Supported modulation modes are - Amplitude Shift Keying (``ask``) - Frequency Shift Keying (``fsk``) - Phase Shift Keying (``psk``) - -Modes that will be supported in the future are - - Quadrature Amplitude Modulation (``qam``) Amplitude Shift Keying From 5f6d4a86d79ddc67bb92a10936c421359da5adc6 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 12 May 2020 14:06:23 -0700 Subject: [PATCH 211/228] Raise ValueError upon encountering bad modulator type in examples --- test/cli/test_yaml_loader.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/cli/test_yaml_loader.py b/test/cli/test_yaml_loader.py index 1696913..06d5b12 100644 --- a/test/cli/test_yaml_loader.py +++ b/test/cli/test_yaml_loader.py @@ -31,6 +31,8 @@ def test_load_doc_examples(): assert isinstance(modulator_list[0],PSKModulator) elif "qam" in yaml_file: assert isinstance(modulator_list[0],QAMModulator) + else: + raise ValueError("Invalid modulator type in examples!") # pragma: no cover @pytest.mark.unit def test_load_doc_examples_staticmethod(): @@ -51,6 +53,8 @@ def test_load_doc_examples_staticmethod(): assert isinstance(modulator_list[0],PSKModulator) elif "qam" in yaml_file: assert isinstance(modulator_list[0],QAMModulator) + else: + raise ValueError("Invalid modulator type in examples!") # pragma: no cover @pytest.mark.unit def test_load_doc_nonexamples(): From 890e5da743339514a83b04796edae422f99a7c25 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 12 May 2020 14:06:52 -0700 Subject: [PATCH 212/228] Check all example configs in CLI roundtrip modulation test --- test/cli/test_commands.py | 46 +++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/test/cli/test_commands.py b/test/cli/test_commands.py index 4cec9a8..128169c 100644 --- a/test/cli/test_commands.py +++ b/test/cli/test_commands.py @@ -4,6 +4,7 @@ ExtendedCommand, FileExistsAndCannotOverwriteException from voicechat_modem_dsp.cli.command_objects import TxFile, RxFile from .testing_utils import MockIO, FileCleanup +import glob import os import pytest @@ -52,27 +53,30 @@ def test_extendedcmd_interactive(): @pytest.mark.unit def test_roundtrip_modulation_cmd(): - with FileCleanup("modulated.wav"): - with FileCleanup("demodulated.dat"): - application = cleo.Application() - application.add(TxFile()) - application.add(RxFile()) - tx_commands=" ".join(["--config docs/specs/examples/ask_1k.yaml", - "--output modulated.wav","--raw","test/cli/testing_utils.py"]) - - command_tx = application.find("transmit_file") - command_tx_tester = cleo.CommandTester(command_tx) - command_tx_tester.execute(tx_commands) - - assert command_tx_tester.status_code == 0 - - rx_commands=" ".join(["--config docs/specs/examples/ask_1k.yaml", - "--output demodulated.dat","modulated.wav"]) - command_rx = application.find("receive_file") - command_rx_tester = cleo.CommandTester(command_rx) - command_rx_tester.execute(rx_commands) - - assert command_rx_tester.status_code == 0 + configs_valid=glob.glob("docs/specs/examples/*.yaml") + file_input="mypy.ini" + for config in configs_valid: + with FileCleanup("modulated.wav"): + with FileCleanup("demodulated.dat"): + application = cleo.Application() + application.add(TxFile()) + application.add(RxFile()) + tx_commands=" ".join(["--config "+config, + "--output modulated.wav","--raw",file_input]) + + command_tx = application.find("transmit_file") + command_tx_tester = cleo.CommandTester(command_tx) + command_tx_tester.execute(tx_commands) + + assert command_tx_tester.status_code == 0 + + rx_commands=" ".join(["--config "+config, + "--output demodulated.dat","modulated.wav"]) + command_rx = application.find("receive_file") + command_rx_tester = cleo.CommandTester(command_rx) + command_rx_tester.execute(rx_commands) + + assert command_rx_tester.status_code == 0 @pytest.mark.unit def test_improper_file_parameters(): From 97181020e202f07c1f6b4336fda3c08596103196 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 12 May 2020 18:30:26 -0700 Subject: [PATCH 213/228] Add test for corruption response upon demodulation --- test/modulators/test_modulator_integrity.py | 42 +++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/test/modulators/test_modulator_integrity.py b/test/modulators/test_modulator_integrity.py index ccaea26..afd1641 100644 --- a/test/modulators/test_modulator_integrity.py +++ b/test/modulators/test_modulator_integrity.py @@ -12,7 +12,7 @@ def get_rand_float(lower, upper): return random.random()*(upper-lower)+lower -@pytest.mark.filterwarnings("ignore") +#@pytest.mark.filterwarnings("ignore") @pytest.mark.unit def test_unit_ask_integrity_novoice(): amplitude_list=list(np.linspace(0.1,1,16)) @@ -31,7 +31,7 @@ def test_unit_ask_integrity_novoice(): assert bitstream==recovered_bitstream -@pytest.mark.filterwarnings("ignore") +#@pytest.mark.filterwarnings("ignore") @pytest.mark.unit def test_unit_ask_integrity_voice(): amplitude_list=list(np.geomspace(0.1,1,16)) @@ -70,7 +70,7 @@ def test_unit_fsk_integrity_bell_202(): assert bitstream==recovered_bitstream -@pytest.mark.filterwarnings("ignore") +#@pytest.mark.filterwarnings("ignore") @pytest.mark.unit def test_unit_fsk_integrity_high_pitch(): frequency_list=[8000,10000,12000,14000] @@ -90,6 +90,42 @@ def test_unit_fsk_integrity_high_pitch(): assert bitstream==recovered_bitstream +@pytest.mark.unit +def test_property_silence_corruption(): + amplitude_list=[0.25,0.5,0.75,1] + phase_list=2*np.pi*np.asarray([0,0.25,0.5,0.75]) + qam_list=[0.5+0.5j,0.5-0.5j,-0.5-0.5j,-0.5+0.5j] + freq_list=[50,100,150,200] + input_sequence=[0,1,3,2,3,1,0,1,2,3] + + ask_mod=ASKModulator(1000,100,amplitude_list,25) + psk_mod=PSKModulator(1000,100,1,phase_list,25) + qam_mod=QAMModulator(1000,100,qam_list,25) + fsk_mod=FSKModulator(1000,1,freq_list,25) + + ask_out=ask_mod.modulate(input_sequence) + psk_out=psk_mod.modulate(input_sequence) + qam_out=qam_mod.modulate(input_sequence) + fsk_out=fsk_mod.modulate(input_sequence) + + ask_out[len(ask_out)//2:]=0 + psk_out[len(psk_out)//2:]=0 + qam_out[len(qam_out)//2:]=0 + fsk_out[len(fsk_out)//2:]=0 + + with pytest.warns(ModulationIntegrityWarning): + ask_recovered_sequence=ask_mod.demodulate(ask_out) + assert -1 in ask_recovered_sequence + with pytest.warns(ModulationIntegrityWarning): + psk_recovered_sequence=psk_mod.demodulate(ask_out) + assert -1 in psk_recovered_sequence + with pytest.warns(ModulationIntegrityWarning): + qam_recovered_sequence=qam_mod.demodulate(ask_out) + assert -1 in qam_recovered_sequence + with pytest.warns(ModulationIntegrityWarning): + fsk_recovered_sequence=fsk_mod.demodulate(ask_out) + assert -1 in fsk_recovered_sequence + @pytest.mark.property def test_property_ask_integrity(): amplitude_list=list(np.linspace(0.1,1,16)) From 11d7e25089f687630bd5a3ef50182cd880ff7dfa Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 12 May 2020 22:04:32 -0700 Subject: [PATCH 214/228] Write docstrings for the bitstream+datastream functions --- voicechat_modem_dsp/encoders/bitstream.py | 10 +++++++++- voicechat_modem_dsp/encoders/encode_pad.py | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/voicechat_modem_dsp/encoders/bitstream.py b/voicechat_modem_dsp/encoders/bitstream.py index 412a858..7a98419 100644 --- a/voicechat_modem_dsp/encoders/bitstream.py +++ b/voicechat_modem_dsp/encoders/bitstream.py @@ -1,3 +1,4 @@ +"""Utility functions to read and write bitstreams (bytes or bytearray)""" from typing import Union, Iterator readable_bytearr=Union[bytes, bytearray] @@ -6,6 +7,7 @@ # Bitstream functions are MSB first def read_bitstream(bitstream: readable_bytearr, position: int) -> bool: + """Return the bit at the given location in the bitstream.""" if position<0 or position>=8*len(bitstream): raise ValueError("Position index is out of range") byteindex=position//8 @@ -13,13 +15,19 @@ def read_bitstream(bitstream: readable_bytearr, position: int) -> bool: return bool((bitstream[byteindex] & (1<> shift_val) def read_bitstream_iterator(bitstream: readable_bytearr) -> Iterator[bool]: + """Create a bitwise interator over the given bitstream.""" for byte in bitstream: for shift_val in range(7,-1,-1): yield bool((byte & (1<> shift_val) def write_bitstream(bitstream: writeable_bytearr, position: int, bit: bool) -> None: - # Modifies bitstream in place so raise TypeError if wrong type + """ + Write the bit into the given location in the bitstream. + + This modifies the bitstream in place, so it must be mutable. + """ + # Raise TypeError if the bitstream is not mutable if isinstance(bitstream, bytes): raise TypeError("Bitstream should be mutable") if position<0 or position>=8*len(bitstream): diff --git a/voicechat_modem_dsp/encoders/encode_pad.py b/voicechat_modem_dsp/encoders/encode_pad.py index ccaa393..a6f5884 100644 --- a/voicechat_modem_dsp/encoders/encode_pad.py +++ b/voicechat_modem_dsp/encoders/encode_pad.py @@ -1,3 +1,11 @@ +""" +Functions that convert between bitstreams and datastreams. + +Bitstreams are bytes or bytearray, and datastreams are List[int]. +The *_encode functions convert a bitstream to a datastream, +and the *_decode functions convert in the opposite direction. +""" + from .bitstream import read_bitstream_iterator, write_bitstream from .bitstream import readable_bytearr, writeable_bytearr From 7632e93e787c652845cb6be37d035470a5d81657 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 14 May 2020 15:39:57 -0700 Subject: [PATCH 215/228] Add checks modulator utils to ensure positive inputs --- voicechat_modem_dsp/modulators/modulator_utils.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 7608f75..61100d2 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -84,6 +84,8 @@ def gaussian_window(fs, sigma_dt): """ Computes a gaussian smoothing filter given sampling rate and sigma time """ + if fs<=0 or sigma_dt<=0: + raise ValueError("Inputs must be positive") sigma=sigma_dt*fs sample_count=np.ceil(6*sigma+1) if sample_count%2==0: @@ -100,6 +102,8 @@ def fred_harris_fir_tap_count(fs, transition_width, db_attenuation): https://dsp.stackexchange.com/questions/37646/filter-order-rule-of-thumb """ #N=[fs/delta(f)]∗[atten(dB)/22] + if fs<=0 or transition_width<=0 or db_attenuation<=0: + raise ValueError("Inputs must be positive") filter_tap_count=fs/transition_width filter_tap_count*=db_attenuation/22 filter_tap_count=int(np.ceil(filter_tap_count)) @@ -116,6 +120,10 @@ def lowpass_fir_filter(fs,cutoff_low,cutoff_high,attenuation=80): raise ValueError("High cutoff must be larger than low cutoff") if cutoff_low>=0.5*fs or cutoff_high>=0.5*fs: raise ValueError("Cutoffs must be lower than Nyquist limit") + if fs<=0: + raise ValueError("Sampling frequency must be positive") + if cutoff_low<=0 or cutoff_high<=0: + raise ValueError("Cutoffs must be positive") tap_count=fred_harris_fir_tap_count(fs,cutoff_high-cutoff_low,attenuation) @@ -146,6 +154,8 @@ def goertzel_iir(freq,fs): """ if freq>=0.5*fs: raise ValueError("Desired peak frequency is too high") + if fs<=0: + raise ValueError("Sampling frequency must be positive") norm_freq=2*np.pi*freq/fs numerator=[1,-np.exp(-1j*norm_freq)] denominator=[1,-2*np.cos(norm_freq),1] From 455ec27a152def8582ef59ae31702e2b01277e58 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Thu, 14 May 2020 15:40:47 -0700 Subject: [PATCH 216/228] Continue polishing Jupyter Notebook documentation --- docs/index.rst | 1 + docs/notebooks/Datastream Upsampling.ipynb | 1358 ++ docs/notebooks/QAM Modulator.ipynb | 14406 ++++++++++++------- 3 files changed, 10846 insertions(+), 4919 deletions(-) create mode 100644 docs/notebooks/Datastream Upsampling.ipynb diff --git a/docs/index.rst b/docs/index.rst index 95f4176..ff59d8d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,6 +10,7 @@ Welcome to Voicechat Modem (DSP Component)'s documentation! :maxdepth: 2 :caption: Contents: + notebooks/Datastream Upsampling notebooks/Pre-Modulation Gaussian Smoothing notebooks/QAM Modulator notebooks/FSK Modulator diff --git a/docs/notebooks/Datastream Upsampling.ipynb b/docs/notebooks/Datastream Upsampling.ipynb new file mode 100644 index 0000000..17831af --- /dev/null +++ b/docs/notebooks/Datastream Upsampling.ipynb @@ -0,0 +1,1358 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Upsampling the Datastream for Modulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are two possible means to upsample the datastream for modulation: a legacy method using Gaussian smoothing, and a (yet to be implemented) method using raised cosine filters that should be preferred once it is available. Both methods append a null symbol at the ends of the message to allow the modulated output to smoothly fade in and out." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from scipy import signal\n", + "from scipy.interpolate import interp1d\n", + "\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import set_matplotlib_formats\n", + "%matplotlib inline\n", + "set_matplotlib_formats('svg')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Gaussian Smoothed Upsampling (Legacy)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The signal is upsampled by extending the previous sample, and then smoothed using a Gaussian filter. The parameters of this filter are determined as described in [the Gaussian parameters notebook](Pre-Modulation Gaussian Smoothing.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "raw_window=signal.windows.gaussian(53, 8.664) # See the other notebook for parameter origins\n", + "gaussian_window=raw_window/np.sum(raw_window)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "orig_time=np.linspace(0,8,9)\n", + "orig_data=np.asarray([0,1,0,-1,0,1,0,-1,0])\n", + "interp_time=np.linspace(0,8,801)\n", + "interp_data=interp1d(orig_time,orig_data,\"previous\",fill_value=0)(interp_time)\n", + "smoothed_data=signal.convolve(interp_data,gaussian_window,\"same\",method=\"fft\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(orig_time, orig_data, marker=\"x\")\n", + "plt.plot(interp_time, interp_data, interp_time, smoothed_data)\n", + "plt.legend([\"Original\", \"Interpolated\", \"Smoothed\"])\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Raised Cosine Filters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Environment (virtualenv_voicechat-modem-dsp-ciksczpw)", + "language": "python", + "name": "virtualenv_voicechat-modem-dsp-ciksczpw" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/notebooks/QAM Modulator.ipynb b/docs/notebooks/QAM Modulator.ipynb index f8eaff1..83e2f06 100644 --- a/docs/notebooks/QAM Modulator.ipynb +++ b/docs/notebooks/QAM Modulator.ipynb @@ -110,10 +110,10 @@ " \n", " \n", + "\" id=\"m071ba90a73\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", "
\n", " \n", @@ -157,7 +157,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -198,7 +198,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -228,7 +228,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -243,7 +243,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -284,7 +284,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -299,7 +299,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -348,7 +348,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -363,7 +363,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -401,10 +401,10 @@ " \n", " \n", + "\" id=\"mf1eb2c0439\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -429,7 +429,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -457,7 +457,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -474,7 +474,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -491,7 +491,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -507,7 +507,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -523,7 +523,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -539,7 +539,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -555,7 +555,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -570,7 +570,7 @@ " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1920,10 +1920,10 @@ " \n", " \n", + "\" id=\"m82acd2eb78\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1959,7 +1959,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1988,7 +1988,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2028,7 +2028,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2076,7 +2076,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2109,7 +2109,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2153,10 +2153,10 @@ " \n", " \n", + "\" id=\"me4860a0bc4\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2169,7 +2169,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2183,7 +2183,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2197,7 +2197,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2243,7 +2243,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2298,7 +2298,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2312,12 +2312,12 @@ " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n" - ], - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "example_sig_fft=np.fft.rfft(example_sig)\n", - "modulated_example_sig_fft=np.fft.rfft(modulated_example_sig)\n", - "example_freqs=np.fft.rfftfreq(len(example_sig),d=0.01)\n", - "plt.plot(example_freqs,np.abs(example_sig_fft),\n", - " example_freqs,np.abs(modulated_example_sig_fft))\n", - "plt.vlines([6],0,100)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Modulation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We express the product modulation as an analytic signal:\n", - "\n", - "$$e^{2 \\pi j f t}\\left(I(t)+jQ(t)\\right)=\n", - "I(t)\\cos(2 \\pi f t)-Q(t)\\sin(2 \\pi f t)\n", - "+jI(t)\\sin(2 \\pi f t)+jQ(t)\\cos(2 \\pi f t)$$\n", - "\n", - "so\n", - "\n", - "$$\\Re\\left(e^{2 \\pi j f t}\\left(I(t)+jQ(t)\\right)\\right)=\n", - "I(t)\\cos(2 \\pi f t)-Q(t)\\sin(2 \\pi f t)$$\n", - "\n", - "We also note that $e^{2 \\pi j f t}\\left(I(t)+jQ(t)\\right)$ has only positive frequency components, and that the real part will have an even Fourier Transform. This demonstrates the information recovery aspect of an analytic signal, including the fact that the real part contains enough information to reconstruct both $I(t)$ and $Q(t)$." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "M 52.203125 31.203125 \n", + "L 52.203125 0 \n", + "L 43.21875 0 \n", + "L 43.21875 8.296875 \n", + "Q 40.140625 3.328125 35.546875 0.953125 \n", + "Q 30.953125 -1.421875 24.3125 -1.421875 \n", + "Q 15.921875 -1.421875 10.953125 3.296875 \n", + "Q 6 8.015625 6 15.921875 \n", + "Q 6 25.140625 12.171875 29.828125 \n", + "Q 18.359375 34.515625 30.609375 34.515625 \n", + "L 43.21875 34.515625 \n", + "L 43.21875 35.40625 \n", + "Q 43.21875 41.609375 39.140625 45 \n", + "Q 35.0625 48.390625 27.6875 48.390625 \n", + "Q 23 48.390625 18.546875 47.265625 \n", + "Q 14.109375 46.140625 10.015625 43.890625 \n", + "L 10.015625 52.203125 \n", + "Q 14.9375 54.109375 19.578125 55.046875 \n", + "Q 24.21875 56 28.609375 56 \n", + "Q 40.484375 56 46.34375 49.84375 \n", + "Q 52.203125 43.703125 52.203125 31.203125 \n", + "z\n", + "\" id=\"DejaVuSans-97\"/>\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" id=\"DejaVuSans-111\"/>\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", "\n" @@ -2966,16 +2738,44 @@ } ], "source": [ - "base_signal=np.concatenate([np.linspace(0,1,30),\n", - " np.linspace(1,1j,len(timearray)-60),\n", - " np.linspace(1j,0,30)])\n", - "plt.plot(timearray,np.real(base_signal),timearray,np.imag(base_signal))\n", + "example_sig_fft=np.fft.rfft(example_sig)\n", + "modulated_example_sig_fft=np.fft.rfft(modulated_example_sig)\n", + "example_freqs=np.fft.rfftfreq(len(example_sig),d=0.01)\n", + "plt.plot(example_freqs,np.abs(example_sig_fft),\n", + " example_freqs,np.abs(modulated_example_sig_fft))\n", + "plt.legend([\"Input Signal\", \"Modulated Signal\"])\n", + "plt.vlines([6],0,100)\n", "plt.show()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We express the product modulation as an analytic signal:\n", + "\n", + "$$e^{2 \\pi j f t}\\left(I(t)+jQ(t)\\right)=\n", + "I(t)\\cos(2 \\pi f t)-Q(t)\\sin(2 \\pi f t)\n", + "+jI(t)\\sin(2 \\pi f t)+jQ(t)\\cos(2 \\pi f t)$$\n", + "\n", + "so\n", + "\n", + "$$\\Re\\left(e^{2 \\pi j f t}\\left(I(t)+jQ(t)\\right)\\right)=\n", + "I(t)\\cos(2 \\pi f t)-Q(t)\\sin(2 \\pi f t)$$\n", + "\n", + "We also note that $e^{2 \\pi j f t}\\left(I(t)+jQ(t)\\right)$ has only positive frequency components, and that the real part will have an even Fourier Transform. This demonstrates the information recovery aspect of an analytic signal, including the fact that the real part contains enough information to reconstruct both $I(t)$ and $Q(t)$." + ] + }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -2985,7 +2785,7 @@ "\n", "\n", - "\n", + "\n", " \n", " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", " \n", @@ -3015,10 +2815,10 @@ " \n", " \n", + "\" id=\"mf98b70b6d4\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3052,7 +2852,7 @@ "z\n", "\" id=\"DejaVuSans-46\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3062,7 +2862,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3093,7 +2893,7 @@ "z\n", "\" id=\"DejaVuSans-53\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3103,7 +2903,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3123,7 +2923,7 @@ "z\n", "\" id=\"DejaVuSans-49\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3133,12 +2933,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3148,7 +2948,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3179,7 +2979,7 @@ "z\n", "\" id=\"DejaVuSans-50\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3189,12 +2989,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3204,7 +3004,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3243,7 +3043,7 @@ "z\n", "\" id=\"DejaVuSans-51\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3253,12 +3053,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3268,7 +3068,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3292,7 +3092,7 @@ "z\n", "\" id=\"DejaVuSans-52\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3306,521 +3106,480 @@ " \n", " \n", + "\" id=\"mbc35d21779\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", + " \n", + " \n", + " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", "\n" @@ -3836,33 +3595,18 @@ } ], "source": [ - "complex_carrier=np.exp(2*1j*np.pi*6*timearray)\n", - "transmitted_signal=np.real(base_signal*complex_carrier)\n", - "plt.plot(timearray,transmitted_signal)\n", + "base_signal=np.concatenate([np.linspace(0,1,30),\n", + " np.linspace(1,1j,len(timearray)-60),\n", + " np.linspace(1j,0,30)])\n", + "plt.plot(timearray,np.real(base_signal),timearray,np.imag(base_signal))\n", + "plt.title(\"Original signal\")\n", + "plt.legend([\"I\",\"Q\"])\n", "plt.show()" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Demodulation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We demodulate my multiplying by the carrier, producing\n", - "\n", - "$$e^{2 \\pi j f t} \\left(I(t)\\cos(2 \\pi f t)-Q(t)\\sin(2 \\pi f t) \\right)=\\frac{1}{2}(I(t)-jQ(t))+k\\left(e^{4\\pi jft}\\right)$$\n", - "\n", - "The high frequency terms can be filtered out to leave behind $\\frac{1}{2}(I(t)-jQ(t))$." - ] - }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -3872,7 +3616,7 @@ "\n", "\n", - "\n", + "\n", " \n", " \n", " \n", " \n", - " \n", " \n", " \n", " \n", - " \n", " \n", @@ -3902,10 +3646,10 @@ " \n", " \n", + "\" id=\"m71572e9c3f\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3939,7 +3683,7 @@ "z\n", "\" id=\"DejaVuSans-46\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3949,7 +3693,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3980,7 +3724,7 @@ "z\n", "\" id=\"DejaVuSans-53\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3990,7 +3734,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4010,7 +3754,7 @@ "z\n", "\" id=\"DejaVuSans-49\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4020,12 +3764,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4035,7 +3779,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4066,7 +3810,7 @@ "z\n", "\" id=\"DejaVuSans-50\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4076,12 +3820,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4091,7 +3835,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4130,7 +3874,7 @@ "z\n", "\" id=\"DejaVuSans-51\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4140,12 +3884,12 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4155,7 +3899,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4179,7 +3923,7 @@ "z\n", "\" id=\"DejaVuSans-52\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4193,14 +3937,14 @@ " \n", " \n", + "\" id=\"m969f67f62c\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "demodulated_signal=transmitted_signal*complex_carrier\n", - "demodulated_signal=2*np.conj(demodulated_signal)\n", - "plt.plot(timearray,np.real(demodulated_signal),timearray,np.imag(demodulated_signal))\n", - "plt.plot(timearray,np.real(base_signal),timearray,np.imag(base_signal))\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" id=\"DejaVuSans-97\"/>\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "complex_carrier=np.exp(2*1j*np.pi*6*timearray)\n", + "transmitted_signal=np.real(base_signal*complex_carrier)\n", + "plt.plot(timearray,transmitted_signal)\n", + "plt.title(\"Modulated Signal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Demodulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We demodulate my multiplying by the carrier, producing\n", + "\n", + "$$e^{2 \\pi j f t} \\left(I(t)\\cos(2 \\pi f t)-Q(t)\\sin(2 \\pi f t) \\right)=\\frac{1}{2}(I(t)-jQ(t))+k\\left(e^{4\\pi jft}\\right)$$\n", + "\n", + "The high frequency terms can be filtered out to leave behind $\\frac{1}{2}(I(t)-jQ(t))$." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "demodulated_signal=transmitted_signal*complex_carrier\n", + "demodulated_signal=2*np.conj(demodulated_signal)\n", + "plt.plot(timearray,np.real(demodulated_signal),timearray,np.imag(demodulated_signal))\n", + "plt.plot(timearray,np.real(base_signal),timearray,np.imag(base_signal))\n", + "plt.legend([\"Received I\", \"Received Q\", \"Original I\", \"Original Q\"])\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "filt=signal.firls(51,[0,4,8,50],[1,1,0,0],fs=100)\n", + "freqresp,resp=signal.freqz(filt,1,fs=100)\n", + "plt.semilogy(freqresp,np.abs(resp))\n", + "plt.title(\"FIR filter response (magnitude)\")\n", + "plt.show()\n", + "plt.plot(freqresp,np.unwrap(np.angle(resp)))\n", + "plt.title(\"FIR filter response (phase)\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6367,11 +9019,11 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", - " \n", " \n", " \n", " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "filt=signal.firls(51,[0,4,8,50],[1,1,0,0],fs=100)\n", - "freqresp,resp=signal.freqz(filt,1,fs=100)\n", - "plt.semilogy(freqresp,np.abs(resp))\n", - "plt.show()\n", - "plt.plot(freqresp,np.unwrap(np.angle(resp)))\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;stroke-width:0.8;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" style=\"fill:#ffffff;opacity:0.8;stroke:#cccccc;stroke-linejoin:miter;\"/>\n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "M 14.796875 27.296875 \n", + "Q 14.796875 17.390625 18.875 11.75 \n", + "Q 22.953125 6.109375 30.078125 6.109375 \n", + "Q 37.203125 6.109375 41.296875 11.75 \n", + "Q 45.40625 17.390625 45.40625 27.296875 \n", + "Q 45.40625 37.203125 41.296875 42.84375 \n", + "Q 37.203125 48.484375 30.078125 48.484375 \n", + "Q 22.953125 48.484375 18.875 42.84375 \n", + "Q 14.796875 37.203125 14.796875 27.296875 \n", + "z\n", + "\" id=\"DejaVuSans-100\"/>\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" id=\"DejaVuSans-110\"/>\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "filtered_demodulated_signal=signal.lfilter(filt,1,demodulated_signal)\n", + "plt.plot(timearray,np.real(filtered_demodulated_signal),\n", + " timearray,np.imag(filtered_demodulated_signal))\n", + "plt.plot(timearray+25*0.01,np.real(base_signal),timearray+25*0.01,np.imag(base_signal))\n", + "plt.legend([\"Recovered I\", \"Recovered Q\", \"Original I\", \"Original Q\"])\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", - " \n", - " \n", + " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "filtered_demodulated_signal=signal.lfilter(filt,1,demodulated_signal)\n", - "plt.plot(timearray,np.real(filtered_demodulated_signal),\n", - " timearray,np.imag(filtered_demodulated_signal))\n", - "plt.plot(timearray+25*0.01,np.real(base_signal),timearray+25*0.01,np.imag(base_signal))\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", - " \n", + " \n", - " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" id=\"DejaVuSans-67\"/>\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" id=\"DejaVuSans-116\"/>\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + "\" id=\"DejaVuSans-68\"/>\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", "\n" @@ -8412,19 +12984,15 @@ "\n", "frequencies=np.fft.fftshift(np.fft.fftfreq(len(base_signal),0.01))\n", "\n", - "plt.plot(frequencies,np.abs(original_fft),\n", - " frequencies,np.abs(transmitted_fft),\n", - " frequencies,np.abs(demodulated_fft),\n", - " frequencies,np.abs(filtered_demodulated_fft))\n", + "plt.semilogy(frequencies,np.abs(original_fft),\n", + " frequencies,np.abs(transmitted_fft),\n", + " frequencies,np.abs(demodulated_fft),\n", + " frequencies,np.abs(filtered_demodulated_fft))\n", + "plt.title(\"Frequency-domain Signal Comparisons\")\n", + "plt.legend([\"Original Signal\", \"Transmitted Signal\",\n", + " \"Demodulated Signal\", \"Recovered Signal\"])\n", "plt.show()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From f5a1aeeb8b8bb5fa73155687f9ff0e4f6bb3a8d5 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 16 May 2020 19:43:19 -0700 Subject: [PATCH 217/228] Adjust docstrings for base modulator object --- voicechat_modem_dsp/modulators/modulator_base.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_base.py b/voicechat_modem_dsp/modulators/modulator_base.py index a360d53..c406e35 100644 --- a/voicechat_modem_dsp/modulators/modulator_base.py +++ b/voicechat_modem_dsp/modulators/modulator_base.py @@ -4,11 +4,10 @@ from typing import Sequence -""" -Base class for other modulator objects -Also contains useful helpers as static attributes -""" class BaseModulator(ABC): + """ + Base class with helper functions for other modulator objects + """ # norm.isf(1/(2*2^8)) sigma_mult_t=2.89 # norm.isf(0.0001) @@ -17,7 +16,9 @@ class BaseModulator(ABC): @abstractmethod def modulate(self, datastream: Sequence[int]) -> ndarray: + """Modulates the datastream into a signal for transmission""" raise NotImplementedError @abstractmethod def demodulate(self, modulated_data: ndarray) -> Sequence[int]: + """Demodulates a signal into a datastream""" raise NotImplementedError From 331263e6813ba561e6fe1ee720458d6c31f44b23 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 16 May 2020 19:53:03 -0700 Subject: [PATCH 218/228] Add back the ignoring of warnings in ASK unit tests Forgot why I put them there originally-oops --- test/modulators/test_modulator_integrity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/modulators/test_modulator_integrity.py b/test/modulators/test_modulator_integrity.py index afd1641..3e84f6f 100644 --- a/test/modulators/test_modulator_integrity.py +++ b/test/modulators/test_modulator_integrity.py @@ -12,7 +12,7 @@ def get_rand_float(lower, upper): return random.random()*(upper-lower)+lower -#@pytest.mark.filterwarnings("ignore") +@pytest.mark.filterwarnings("ignore") @pytest.mark.unit def test_unit_ask_integrity_novoice(): amplitude_list=list(np.linspace(0.1,1,16)) @@ -31,7 +31,7 @@ def test_unit_ask_integrity_novoice(): assert bitstream==recovered_bitstream -#@pytest.mark.filterwarnings("ignore") +@pytest.mark.filterwarnings("ignore") @pytest.mark.unit def test_unit_ask_integrity_voice(): amplitude_list=list(np.geomspace(0.1,1,16)) From 054a6181483c1e1ad85a56a4207d6eaceb46de8f Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 16 May 2020 19:53:28 -0700 Subject: [PATCH 219/228] Adjust tests for negative parameters in filter utilities --- test/modulators/test_modulator_utils.py | 11 ++++++++++- voicechat_modem_dsp/modulators/modulator_utils.py | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/test/modulators/test_modulator_utils.py b/test/modulators/test_modulator_utils.py index 11d443c..7f323e9 100644 --- a/test/modulators/test_modulator_utils.py +++ b/test/modulators/test_modulator_utils.py @@ -17,13 +17,22 @@ def test_property_generate_timearray(): assert np.abs(timearr[-1]-1) < epsilon @pytest.mark.unit -def test_filtergen_error(): +def test_unit_filtergen_parameters(): with pytest.raises(ValueError): filt=lowpass_fir_filter(2000,500,100) with pytest.raises(ValueError): filt=lowpass_fir_filter(2000,1000,4000) with pytest.raises(ValueError): filt=goertzel_iir(3,2) + with pytest.raises(ValueError): + filt=goertzel_iir(-3,2) + # These should not error + goertzel_iir(1,8) + goertzel_iir(-1,8) + with pytest.raises(ValueError): + filt=lowpass_fir_filter(-100,20,40) + with pytest.raises(ValueError): + filt=goertzel_iir(2,-16) @pytest.mark.unit def test_unit_average_int(): diff --git a/voicechat_modem_dsp/modulators/modulator_utils.py b/voicechat_modem_dsp/modulators/modulator_utils.py index 61100d2..35340d6 100644 --- a/voicechat_modem_dsp/modulators/modulator_utils.py +++ b/voicechat_modem_dsp/modulators/modulator_utils.py @@ -152,10 +152,10 @@ def goertzel_iir(freq,fs): Derivation of formula from https://www.dsprelated.com/showarticle/796.php """ - if freq>=0.5*fs: - raise ValueError("Desired peak frequency is too high") if fs<=0: raise ValueError("Sampling frequency must be positive") + if abs(freq)>=0.5*fs: + raise ValueError("Desired peak frequency is too high") norm_freq=2*np.pi*freq/fs numerator=[1,-np.exp(-1j*norm_freq)] denominator=[1,-2*np.cos(norm_freq),1] From f4acf2afa208043bec1b5c875903320bad813709 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 17 May 2020 18:18:44 -0700 Subject: [PATCH 220/228] Ignore warnings for fsk_integrity_high_pitch (again) --- test/modulators/test_modulator_integrity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/modulators/test_modulator_integrity.py b/test/modulators/test_modulator_integrity.py index 3e84f6f..0873bb4 100644 --- a/test/modulators/test_modulator_integrity.py +++ b/test/modulators/test_modulator_integrity.py @@ -70,7 +70,7 @@ def test_unit_fsk_integrity_bell_202(): assert bitstream==recovered_bitstream -#@pytest.mark.filterwarnings("ignore") +@pytest.mark.filterwarnings("ignore") @pytest.mark.unit def test_unit_fsk_integrity_high_pitch(): frequency_list=[8000,10000,12000,14000] From fb686132079c7591899c82612728d392f22d6437 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 17 May 2020 19:47:50 -0700 Subject: [PATCH 221/228] Parallelize property tests with pytest-cov and pytest-xdist --- Pipfile | 4 ++- Pipfile.lock | 82 ++++++++++++++++++++++++++++++------------- run_tests_coverage.sh | 10 +++--- 3 files changed, 67 insertions(+), 29 deletions(-) diff --git a/Pipfile b/Pipfile index e9898d6..f34ecea 100644 --- a/Pipfile +++ b/Pipfile @@ -15,9 +15,11 @@ matplotlib = "*" [dev-packages] pytest = "*" coverage = "*" -sphinx = "*" +Sphinx = "*" nbsphinx = "*" itikz = "*" +pytest-xdist = "*" +pytest-cov = "*" [dev-packages.mypy] version = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 404f114..cf52947 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d5de3bdc80a19f796d5b0b8133b18f5601e236425a8606df586623f4a7f748fe" + "sha256": "77a785928dee7971069a27901647776dbbf6256f09f3d03a600fb5dd7b2619da" }, "pipfile-spec": 6, "requires": {}, @@ -207,28 +207,25 @@ }, "protobuf": { "hashes": [ - "sha256:0bae429443cc4748be2aadfdaf9633297cfaeb24a9a02d0ab15849175ce90fab", - "sha256:24e3b6ad259544d717902777b33966a1a069208c885576254c112663e6a5bb0f", - "sha256:2affcaba328c4662f3bc3c0e9576ea107906b2c2b6422344cdad961734ff6b93", - "sha256:310a7aca6e7f257510d0c750364774034272538d51796ca31d42c3925d12a52a", - "sha256:52e586072612c1eec18e1174f8e3bb19d08f075fc2e3f91d3b16c919078469d0", - "sha256:73152776dc75f335c476d11d52ec6f0f6925774802cd48d6189f4d5d7fe753f4", - "sha256:7774bbbaac81d3ba86de646c39f154afc8156717972bf0450c9dbfa1dc8dbea2", - "sha256:82d7ac987715d8d1eb4068bf997f3053468e0ce0287e2729c30601feb6602fee", - "sha256:8eb9c93798b904f141d9de36a0ba9f9b73cc382869e67c9e642c0aba53b0fc07", - "sha256:adf0e4d57b33881d0c63bb11e7f9038f98ee0c3e334c221f0858f826e8fb0151", - "sha256:c40973a0aee65422d8cb4e7d7cbded95dfeee0199caab54d5ab25b63bce8135a", - "sha256:c77c974d1dadf246d789f6dad1c24426137c9091e930dbf50e0a29c1fcf00b1f", - "sha256:dd9aa4401c36785ea1b6fff0552c674bdd1b641319cb07ed1fe2392388e9b0d7", - "sha256:e11df1ac6905e81b815ab6fd518e79be0a58b5dc427a2cf7208980f30694b956", - "sha256:e2f8a75261c26b2f5f3442b0525d50fd79a71aeca04b5ec270fc123536188306", - "sha256:e512b7f3a4dd780f59f1bf22c302740e27b10b5c97e858a6061772668cd6f961", - "sha256:ef2c2e56aaf9ee914d3dccc3408d42661aaf7d9bb78eaa8f17b2e6282f214481", - "sha256:fac513a9dc2a74b99abd2e17109b53945e364649ca03d9f7a0b96aa8d1807d0a", - "sha256:fdfb6ad138dbbf92b5dbea3576d7c8ba7463173f7d2cb0ca1bd336ec88ddbd80" + "sha256:00c2c276aca3af220d422e6a8625b1f5399c821c9b6f1c83e8a535aa8f48cc6c", + "sha256:0d69d76b00d0eb5124cb33a34a793383a5bbbf9ac3e633207c09988717c5da85", + "sha256:1c55277377dd35e508e9d86c67a545f6d8d242d792af487678eeb75c07974ee2", + "sha256:35bc1b96241b8ea66dbf386547ef2e042d73dcc0bf4b63566e3ef68722bb24d1", + "sha256:47a541ac44f2dcc8d49b615bcf3ed7ba4f33af9791118cecc3d17815fab652d9", + "sha256:61364bcd2d85277ab6155bb7c5267e6a64786a919f1a991e29eb536aa5330a3d", + "sha256:7aaa820d629f8a196763dd5ba21fd272fa038f775a845a52e21fa67862abcd35", + "sha256:9593a6cdfc491f2caf62adb1c03170e9e8748d0a69faa2b3970e39a92fbd05a2", + "sha256:95f035bbafec7dbaa0f1c72eda8108b763c1671fcb6e577e93da2d52eb47fbcf", + "sha256:9d6a517ce33cbdc64b52a17c56ce17b0b20679c945ed7420e7c6bc6686ff0494", + "sha256:a7532d971e4ab2019a9f6aa224b209756b6b9e702940ca85a4b1ed1d03f45396", + "sha256:b4e8ecb1eb3d011f0ccc13f8bb0a2d481aa05b733e6e22e9d46a3f61dbbef0de", + "sha256:bb1aced9dcebc46f0b320f24222cc8ffdfd2e47d2bafd4d2e5913cc6f7e3fc98", + "sha256:ccce142ebcfbc35643a5012cf398497eb18e8d021333cced4d5401f034a8cef5", + "sha256:d538eecc0b80accfb73c8167f39aaa167a5a50f31b1295244578c8eff8e9d602", + "sha256:eab18765eb5c7bad1b2de7ae3774192b46e1873011682e36bcd70ccf75f2748a" ], "index": "pypi", - "version": "==3.11.3" + "version": "==3.12.0" }, "ptyprocess": { "hashes": [ @@ -408,6 +405,13 @@ ], "version": "==0.7.12" }, + "apipkg": { + "hashes": [ + "sha256:37228cda29411948b422fae072f57e31d3396d2ee1c9783775980ee9c9990af6", + "sha256:58587dd4dc3daefad0487f6d9ae32b4542b185e1c36db6993290e7c41ca2b47c" + ], + "version": "==1.5" + }, "attrs": { "hashes": [ "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", @@ -508,6 +512,13 @@ ], "version": "==0.3" }, + "execnet": { + "hashes": [ + "sha256:cacb9df31c9680ec5f95553976c4da484d407e85e41c83cb812aa014f0eddc50", + "sha256:d4efd397930c46415f62f8a31388d6be4f27a91d7550eb79bc64a756e0056547" + ], + "version": "==1.7.1" + }, "idna": { "hashes": [ "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", @@ -613,10 +624,10 @@ }, "more-itertools": { "hashes": [ - "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", - "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" + "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be", + "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982" ], - "version": "==8.2.0" + "version": "==8.3.0" }, "mypy": { "hashes": [ @@ -754,6 +765,29 @@ "index": "pypi", "version": "==5.4.2" }, + "pytest-cov": { + "hashes": [ + "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b", + "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626" + ], + "index": "pypi", + "version": "==2.8.1" + }, + "pytest-forked": { + "hashes": [ + "sha256:1805699ed9c9e60cb7a8179b8d4fa2b8898098e82d229b0825d8095f0f261100", + "sha256:1ae25dba8ee2e56fb47311c9638f9e58552691da87e82d25b0ce0e4bf52b7d87" + ], + "version": "==1.1.3" + }, + "pytest-xdist": { + "hashes": [ + "sha256:1d4166dcac69adb38eeaedb88c8fada8588348258a3492ab49ba9161f2971129", + "sha256:ba5ec9fde3410bd9a116ff7e4f26c92e02fa3d27975ef3ad03f330b3d4b54e91" + ], + "index": "pypi", + "version": "==1.32.0" + }, "pytz": { "hashes": [ "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", diff --git a/run_tests_coverage.sh b/run_tests_coverage.sh index 0cf8dd5..740fcb1 100644 --- a/run_tests_coverage.sh +++ b/run_tests_coverage.sh @@ -1,10 +1,12 @@ #!/bin/sh -COVERAGE_FILE='.coverage.unit' coverage run --branch --source 'test,voicechat_modem_dsp' -m pytest -v -m unit +# There may be warnings about "no data collected", but this seems to work correctly +# Only parallelize long property tests for now +# A cmd unit test needs refactoring to resolve filesystem races first +pytest --cov=voicechat_modem_dsp --cov=test --cov-report= --cov-branch -v -m unit test_status_unit=$? -COVERAGE_FILE='.coverage.property' coverage run --branch --source 'test,voicechat_modem_dsp' -m pytest -v -m property +pytest --cov=voicechat_modem_dsp --cov=test --cov-report= --cov-branch --cov-append -n auto -v -m property test_status_property=$? -coverage combine -coverage report -m +coverage report coverage xml -i if [ $test_status_unit -eq 0 ] && [ $test_status_property -eq 0 ]; then exit 0 From 34a8bb9bf49179bee4632f6ce751736dd9de9f79 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 17 May 2020 20:11:06 -0700 Subject: [PATCH 222/228] Add new dev dependencies to Travis list --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 81dc8d7..e2aa13c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ install: - pipenv lock -r > requirements.txt - cat requirements.txt - pip install -r requirements.txt - - pip install pytest coverage + - pip install pytest coverage pytest-cov pytest-xdist # Dev dependencies - if [ "$MYPY" = true ]; then pip install mypy; pip install -e git+https://github.com/numpy/numpy-stubs.git#egg=numpy-stubs; fi script: From a638d7c47e364bc5c6cc6fa6989c296ede7979a9 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sun, 17 May 2020 21:29:49 -0700 Subject: [PATCH 223/228] Include upgrade command in travis dev dependencies --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e2aa13c..3421112 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ install: - pipenv lock -r > requirements.txt - cat requirements.txt - pip install -r requirements.txt - - pip install pytest coverage pytest-cov pytest-xdist # Dev dependencies + - pip install -U pytest coverage pytest-cov pytest-xdist # Dev dependencies - if [ "$MYPY" = true ]; then pip install mypy; pip install -e git+https://github.com/numpy/numpy-stubs.git#egg=numpy-stubs; fi script: From 7102bf34201becc4f908080480a93660a0cf6cf3 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 26 May 2020 22:25:20 -0700 Subject: [PATCH 224/228] Add the interval offset to the end location in demodulation loops --- voicechat_modem_dsp/modulators/modulator_ask.py | 2 +- voicechat_modem_dsp/modulators/modulator_qam.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index faa3c03..52e2d05 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -117,7 +117,7 @@ def demodulate(self, modulated_data): interval_begin=interval_offset+i*samples_per_symbol # Perform min in order to account for floating point weirdness interval_end=min(interval_begin+samples_per_symbol, - len(modulated_data)-1) + interval_offset+len(modulated_data)-1) # Shrink interval by previously calculated transition width # Skip doing so for first and last sample diff --git a/voicechat_modem_dsp/modulators/modulator_qam.py b/voicechat_modem_dsp/modulators/modulator_qam.py index 0aa4a98..b116897 100644 --- a/voicechat_modem_dsp/modulators/modulator_qam.py +++ b/voicechat_modem_dsp/modulators/modulator_qam.py @@ -128,7 +128,7 @@ def demodulate(self, modulated_data): interval_begin=interval_offset+i*samples_per_symbol # Perform min in order to account for floating point weirdness interval_end=min(interval_begin+samples_per_symbol, - len(modulated_data)-1) + interval_offset+len(modulated_data)-1) # Shrink interval by previously calculated transition width # Skip doing so for first and last sample From 9c98c2686608d87036fe97c3a660342bcfb1d7e2 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 26 May 2020 22:28:43 -0700 Subject: [PATCH 225/228] Use overlap-add convolution method for demodulation filtering --- voicechat_modem_dsp/modulators/modulator_ask.py | 3 ++- voicechat_modem_dsp/modulators/modulator_qam.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/voicechat_modem_dsp/modulators/modulator_ask.py b/voicechat_modem_dsp/modulators/modulator_ask.py index 52e2d05..a3ffbe1 100644 --- a/voicechat_modem_dsp/modulators/modulator_ask.py +++ b/voicechat_modem_dsp/modulators/modulator_ask.py @@ -99,7 +99,8 @@ def demodulate(self, modulated_data): filt_delay=(len(fir_filt)-1)//2 # First append filt_delay number of zeros to incoming signal demod_amplitude=np.pad(demod_amplitude,(0,filt_delay)) - filtered_demod_amplitude=np.abs(signal.lfilter(fir_filt,1,demod_amplitude)) + filtered_demod_amplitude=np.abs(signal.oaconvolve( + fir_filt,demod_amplitude,"full")) # Extract the original amplitudes via averaging of plateau diff --git a/voicechat_modem_dsp/modulators/modulator_qam.py b/voicechat_modem_dsp/modulators/modulator_qam.py index b116897..9ce1797 100644 --- a/voicechat_modem_dsp/modulators/modulator_qam.py +++ b/voicechat_modem_dsp/modulators/modulator_qam.py @@ -110,7 +110,7 @@ def demodulate(self, modulated_data): filt_delay=(len(fir_filt)-1)//2 # First append filt_delay number of zeros to incoming signal demod_signal=np.pad(demod_signal,(0,filt_delay)) - filtered_demod_signal=signal.lfilter(fir_filt,1,demod_signal) + filtered_demod_signal=signal.oaconvolve(fir_filt,demod_signal,"full") # Extract the original amplitudes via averaging of plateau From 31bdb8b87a73898e8cb321c6b609373db7ea0831 Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 26 May 2020 22:50:53 -0700 Subject: [PATCH 226/228] Enforce SciPy>=1.4.0 and update Pipfile.lock accordingly --- Pipfile | 2 +- Pipfile.lock | 72 ++++++++++++++++++++++++++-------------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Pipfile b/Pipfile index f34ecea..cc835c7 100644 --- a/Pipfile +++ b/Pipfile @@ -5,7 +5,7 @@ name = "pypi" [packages] numpy = "*" -scipy = "*" +scipy = {version = ">=1.4.0"} protobuf = "*" ipykernel = "*" cleo = "*" diff --git a/Pipfile.lock b/Pipfile.lock index cf52947..f1e1c68 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "77a785928dee7971069a27901647776dbbf6256f09f3d03a600fb5dd7b2619da" + "sha256": "2150a7f0498a31c5c10dc3c06905f52a7883fb38ddea77079f2bd421881f1de0" }, "pipfile-spec": 6, "requires": {}, @@ -60,11 +60,11 @@ }, "ipykernel": { "hashes": [ - "sha256:003c9c1ab6ff87d11f531fee2b9ca59affab19676fc6b2c21da329aef6e73499", - "sha256:2937373c356fa5b634edb175c5ea0e4b25de8008f7c194f2d49cfbd1f9c970a8" + "sha256:731adb3f2c4ebcaff52e10a855ddc87670359a89c9c784d711e62d66fccdafae", + "sha256:a8362e3ae365023ca458effe93b026b8cdadc0b73ff3031472128dd8a2cf0289" ], "index": "pypi", - "version": "==5.2.1" + "version": "==5.3.0" }, "ipython": { "hashes": [ @@ -207,25 +207,25 @@ }, "protobuf": { "hashes": [ - "sha256:00c2c276aca3af220d422e6a8625b1f5399c821c9b6f1c83e8a535aa8f48cc6c", - "sha256:0d69d76b00d0eb5124cb33a34a793383a5bbbf9ac3e633207c09988717c5da85", - "sha256:1c55277377dd35e508e9d86c67a545f6d8d242d792af487678eeb75c07974ee2", - "sha256:35bc1b96241b8ea66dbf386547ef2e042d73dcc0bf4b63566e3ef68722bb24d1", - "sha256:47a541ac44f2dcc8d49b615bcf3ed7ba4f33af9791118cecc3d17815fab652d9", - "sha256:61364bcd2d85277ab6155bb7c5267e6a64786a919f1a991e29eb536aa5330a3d", - "sha256:7aaa820d629f8a196763dd5ba21fd272fa038f775a845a52e21fa67862abcd35", - "sha256:9593a6cdfc491f2caf62adb1c03170e9e8748d0a69faa2b3970e39a92fbd05a2", - "sha256:95f035bbafec7dbaa0f1c72eda8108b763c1671fcb6e577e93da2d52eb47fbcf", - "sha256:9d6a517ce33cbdc64b52a17c56ce17b0b20679c945ed7420e7c6bc6686ff0494", - "sha256:a7532d971e4ab2019a9f6aa224b209756b6b9e702940ca85a4b1ed1d03f45396", - "sha256:b4e8ecb1eb3d011f0ccc13f8bb0a2d481aa05b733e6e22e9d46a3f61dbbef0de", - "sha256:bb1aced9dcebc46f0b320f24222cc8ffdfd2e47d2bafd4d2e5913cc6f7e3fc98", - "sha256:ccce142ebcfbc35643a5012cf398497eb18e8d021333cced4d5401f034a8cef5", - "sha256:d538eecc0b80accfb73c8167f39aaa167a5a50f31b1295244578c8eff8e9d602", - "sha256:eab18765eb5c7bad1b2de7ae3774192b46e1873011682e36bcd70ccf75f2748a" + "sha256:04d0b2bd99050d09393875a5a25fd12337b17f3ac2e29c0c1b8e65b277cbfe72", + "sha256:05288e44638e91498f13127a3699a6528dec6f9d3084d60959d721bfb9ea5b98", + "sha256:175d85370947f89e33b3da93f4ccdda3f326bebe3e599df5915ceb7f804cd9df", + "sha256:440a8c77531b3652f24999b249256ed01fd44c498ab0973843066681bd276685", + "sha256:49fb6fab19cd3f30fa0e976eeedcbf2558e9061e5fa65b4fe51ded1f4002e04d", + "sha256:4c7cae1f56056a4a2a2e3b00b26ab8550eae738bd9548f4ea0c2fcb88ed76ae5", + "sha256:519abfacbb421c3591d26e8daf7a4957763428db7267f7207e3693e29f6978db", + "sha256:60f32af25620abc4d7928d8197f9f25d49d558c5959aa1e08c686f974ac0b71a", + "sha256:613ac49f6db266fba243daf60fb32af107cfe3678e5c003bb40a381b6786389d", + "sha256:954bb14816edd24e746ba1a6b2d48c43576393bbde2fb8e1e3bd6d4504c7feac", + "sha256:9b1462c033a2cee7f4e8eb396905c69de2c532c3b835ff8f71f8e5fb77c38023", + "sha256:c0767f4d93ce4288475afe0571663c78870924f1f8881efd5406c10f070c75e4", + "sha256:c45f5980ce32879391144b5766120fd7b8803129f127ce36bd060dd38824801f", + "sha256:eeb7502f59e889a88bcb59f299493e215d1864f3d75335ea04a413004eb4fe24", + "sha256:fdb1742f883ee4662e39fcc5916f2725fec36a5191a52123fec60f8c53b70495", + "sha256:fe554066c4962c2db0a1d4752655223eb948d2bfa0fb1c4a7f2c00ec07324f1c" ], "index": "pypi", - "version": "==3.12.0" + "version": "==3.12.1" }, "ptyprocess": { "hashes": [ @@ -356,10 +356,10 @@ }, "six": { "hashes": [ - "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", - "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "version": "==1.14.0" + "version": "==1.15.0" }, "strictyaml": { "hashes": [ @@ -712,10 +712,10 @@ }, "packaging": { "hashes": [ - "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", - "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" + "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", + "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" ], - "version": "==20.3" + "version": "==20.4" }, "pandocfilters": { "hashes": [ @@ -767,11 +767,11 @@ }, "pytest-cov": { "hashes": [ - "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b", - "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626" + "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322", + "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424" ], "index": "pypi", - "version": "==2.8.1" + "version": "==2.9.0" }, "pytest-forked": { "hashes": [ @@ -804,10 +804,10 @@ }, "six": { "hashes": [ - "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", - "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "version": "==1.14.0" + "version": "==1.15.0" }, "snowballstemmer": { "hashes": [ @@ -818,11 +818,11 @@ }, "sphinx": { "hashes": [ - "sha256:62edfd92d955b868d6c124c0942eba966d54b5f3dcb4ded39e65f74abac3f572", - "sha256:f5505d74cf9592f3b997380f9bdb2d2d0320ed74dd69691e3ee0644b956b8d83" + "sha256:779a519adbd3a70fc7c468af08c5e74829868b0a5b34587b33340e010291856c", + "sha256:ea64df287958ee5aac46be7ac2b7277305b0381d213728c3a49d8bb9b8415807" ], "index": "pypi", - "version": "==3.0.3" + "version": "==3.0.4" }, "sphinxcontrib-applehelp": { "hashes": [ From fb4ff0a6d966168975069aacd2847cfb4f8aa22d Mon Sep 17 00:00:00 2001 From: rlee287 Date: Tue, 2 Jun 2020 23:08:15 -0700 Subject: [PATCH 227/228] Remove filename clash in unit tests, allowing them to be run in parallel --- run_tests_coverage.sh | 2 +- test/cli/test_commands.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/run_tests_coverage.sh b/run_tests_coverage.sh index 740fcb1..9b1eec5 100644 --- a/run_tests_coverage.sh +++ b/run_tests_coverage.sh @@ -2,7 +2,7 @@ # There may be warnings about "no data collected", but this seems to work correctly # Only parallelize long property tests for now # A cmd unit test needs refactoring to resolve filesystem races first -pytest --cov=voicechat_modem_dsp --cov=test --cov-report= --cov-branch -v -m unit +pytest --cov=voicechat_modem_dsp --cov=test --cov-report= --cov-branch -n auto -v -m unit test_status_unit=$? pytest --cov=voicechat_modem_dsp --cov=test --cov-report= --cov-branch --cov-append -n auto -v -m property test_status_property=$? diff --git a/test/cli/test_commands.py b/test/cli/test_commands.py index 128169c..9e6ce1f 100644 --- a/test/cli/test_commands.py +++ b/test/cli/test_commands.py @@ -21,12 +21,13 @@ def test_extendedcmd_noninteractive(): extended_cmd.confirm_file_writable("__init__.py") # Cannot write to directory + directory_name="nonsense_dir_noninteractive" try: - os.mkdir("nonsense") + os.mkdir(directory_name) with pytest.raises(FileExistsAndCannotOverwriteException): - extended_cmd.confirm_file_writable("nonsense") + extended_cmd.confirm_file_writable(directory_name) finally: - os.rmdir("nonsense") + os.rmdir(directory_name) @pytest.mark.unit def test_extendedcmd_interactive(): @@ -36,12 +37,13 @@ def test_extendedcmd_interactive(): # Can write to nonexistent file extended_cmd.confirm_file_writable("garbage") # Cannot write to directory + directory_name="nonsense_dir_interactive" try: - os.mkdir("nonsense") + os.mkdir(directory_name) with pytest.raises(FileExistsAndCannotOverwriteException): - extended_cmd.confirm_file_writable("nonsense") + extended_cmd.confirm_file_writable(directory_name) finally: - os.rmdir("nonsense") + os.rmdir(directory_name) # Cannot write to existent file when nonconfirmed with pytest.raises(FileExistsAndCannotOverwriteException): From 21629fe2e4b49a111349f07c27a4625bb48c4b1a Mon Sep 17 00:00:00 2001 From: rlee287 Date: Sat, 5 Dec 2020 13:31:43 -0800 Subject: [PATCH 228/228] Simplify bitstream read slightly and add tests (cherry picked from commit b2d71be2017d76dcc9467ecb303fd77e22071488) --- test/encoders/test_bitstream.py | 25 ++++++++++++++++++++++- voicechat_modem_dsp/encoders/bitstream.py | 4 ++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/test/encoders/test_bitstream.py b/test/encoders/test_bitstream.py index 439358f..d2b069f 100644 --- a/test/encoders/test_bitstream.py +++ b/test/encoders/test_bitstream.py @@ -5,11 +5,34 @@ from voicechat_modem_dsp.encoders.bitstream import * @pytest.mark.unit -def test_unit_bitstream_read(): +def test_unit_bitstream_read_alternating(): bitstream=b"\x55\x55" for i in range(8*len(bitstream)): assert read_bitstream(bitstream,i)==bool(i%2) +@pytest.mark.unit +def test_unit_bitstream_write_alternating(): + bitstream=bytearray(b"\x00\x00") + for i in range(8*len(bitstream)): + write_bitstream(bitstream, i, i%2) + assert bitstream==b"\x55\x55" + +@pytest.mark.unit +def test_unit_bitstream_read_onehotseq(): + bitstream=bytes([1,2,4,8,16,32,64,128]) + for i, element in enumerate(bitstream): + for j in range(8): + bit_read=read_bitstream(bitstream, i*8+j) + assert bit_read == (i == 7-j) + +@pytest.mark.unit +def test_unit_bitstream_write_onehotseq(): + bitstream=bytearray([0]*8) + for i, element in enumerate(bitstream): + for j in range(8): + write_bitstream(bitstream, i*8+j, (i==7-j)) + assert bitstream==bytearray([1,2,4,8,16,32,64,128]) + @pytest.mark.unit def test_unit_bitstream_read_iterator(): bitstream=b"\x00\xff\x55" diff --git a/voicechat_modem_dsp/encoders/bitstream.py b/voicechat_modem_dsp/encoders/bitstream.py index 7a98419..bc8877e 100644 --- a/voicechat_modem_dsp/encoders/bitstream.py +++ b/voicechat_modem_dsp/encoders/bitstream.py @@ -12,13 +12,13 @@ def read_bitstream(bitstream: readable_bytearr, position: int) -> bool: raise ValueError("Position index is out of range") byteindex=position//8 shift_val=7-position%8 - return bool((bitstream[byteindex] & (1<> shift_val) + return bool(bitstream[byteindex] & (1< Iterator[bool]: """Create a bitwise interator over the given bitstream.""" for byte in bitstream: for shift_val in range(7,-1,-1): - yield bool((byte & (1<> shift_val) + yield bool(byte & (1< None: