├── .gitignore ├── .gitmodules ├── .readthedocs.yaml ├── .travis.yml ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── _config.yml ├── docs ├── Makefile ├── make.bat ├── requirements.txt └── source │ ├── coeff2header.rst │ ├── conf.py │ ├── digitalcom.rst │ ├── fec_conv.rst │ ├── fir_design_helper.rst │ ├── iir_design_helper.rst │ ├── index.rst │ ├── logo.png │ ├── multirate_helper.rst │ ├── nb_examples.rst │ ├── nb_examples │ ├── 300ppi │ │ ├── Block_Codes.PNG │ │ ├── Cyclic_Decoder.PNG │ │ ├── Cyclic_Encoder.PNG │ │ ├── Decimator_Top_Level@300ppi.png │ │ ├── FIR_Kaiser_Equiripple_Table@300ppi.png │ │ ├── FIR_Lowpass_Highpass_Bandpass_Bandstop@300ppi.png │ │ ├── IIR_Lowpass_Highpass_Bandpass_Bandstop@300ppi.png │ │ ├── IIR_Table@300ppi.png │ │ ├── Interactive_FM_Rx@300ppi.png │ │ ├── Interpolator_Top_Level@300ppi.png │ │ ├── Jupyter_concurrent_tasks@300ppi.png │ │ ├── Multirate_Table1@300ppi.png │ │ ├── Multirate_Table2@300ppi.png │ │ ├── Multirate_Table3@300ppi.png │ │ ├── Probe_Locations@300ppi.png │ │ └── RTLSDR_Streaming_Block@300ppi.png │ ├── Block_Codes.ipynb │ ├── Continuous-Time Signals and Systems using sigsys.ipynb │ ├── Convolutional_Codes.ipynb │ ├── FIR_and_IIR_Filter_Design.ipynb │ ├── Multirate_Processing.ipynb │ └── audio_files │ │ ├── FIR_BPF_2700_3200_4800_5300_p5dB_50dB_48k.csv │ │ ├── Loop_through_noise_SA_iMic.csv │ │ ├── Music_Test.wav │ │ ├── ThreeBand_Peak_100_p20_1k_p10_8k_m10_fs_48k.csv │ │ ├── cross_panning.png │ │ ├── left_right_gain.png │ │ ├── music_buffer_plot.png │ │ ├── pyaudio_dsp_IO.png │ │ ├── start_stop_stream.png │ │ └── three_band_widgets.png │ ├── sigsys.rst │ └── synchronization.rst ├── logo.png ├── pyproject.toml ├── requirements.txt ├── setup.cfg ├── setup.py ├── src └── sk_dsp_comm │ ├── __init__.py │ ├── __version__.py │ ├── ca1thru37.txt │ ├── coeff2header.py │ ├── digitalcom.py │ ├── fec_block.py │ ├── fec_conv.py │ ├── fir_design_helper.py │ ├── iir_design_helper.py │ ├── multirate_helper.py │ ├── sigsys.py │ └── synchronization.py ├── tests ├── CA_1.h ├── CA_12.h ├── __init__.py ├── sandbox.py ├── sig_mean_var.h ├── test_coeff2header.py ├── test_digitalcom.py ├── test_fec_conv.py ├── test_helper.py ├── test_imports.py └── test_sigsys.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | *.pyc 4 | scikit_dsp_comm.egg-info/ 5 | sk_dsp_comm/__pycache__/ 6 | Scikit-DSP-Comm_Issues_to_Solve.md 7 | 8 | # Files generated by setup.py 9 | dist/ 10 | build/ 11 | 12 | # File generated by setup.py using MANIFEST.in 13 | MANIFEST 14 | 15 | # Built doc files (cd doc; make html) 16 | doc/_build/ 17 | doc/sphinx/ 18 | docs/source/readme.md 19 | 20 | # Tox files 21 | .tox/ 22 | 23 | # IPython Notebook Checkpoints 24 | .ipynb_checkpoints/ 25 | 26 | # Backup files 27 | *~ 28 | .vscode/* 29 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/sphinxext"] 2 | path = docs/sphinxext 3 | url = https://github.com/numpy/numpydoc.git 4 | [submodule "docs/source/_static/scipy-mathjax"] 5 | path = docs/source/_static/scipy-mathjax 6 | url = https://github.com/scipy/scipy-mathjax.git 7 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-22.04 4 | tools: 5 | python: "3.10" 6 | sphinx: 7 | configuration: docs/source/conf.py 8 | python: 9 | install: 10 | - requirements: docs/requirements.txt 11 | - method: pip 12 | path: . -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | services: 3 | - xvfb 4 | language: python 5 | python: 6 | - "3.7" 7 | - "3.8" 8 | - "3.9" 9 | - "3.10-dev" 10 | install: 11 | - pip install -U importlib-metadata 12 | - pip install tox-travis 13 | script: tox -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, Mark Wickert 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include requirements.txt 3 | include src/sk_dsp_comm/__version__.py 4 | include src/sk_dsp_comm/ca1thru37.txt 5 | include logo.png 6 | include Capture_Buffer.png 7 | include two_channel_stream.png 8 | include LICENSE.md 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](logo.png) 2 | 3 | # scikit-dsp-comm 4 | 5 | [![pypi](https://img.shields.io/pypi/v/scikit-dsp-comm.svg)](https://pypi.python.org/pypi/scikit-dsp-comm) 6 | [![Anaconda-Server Badge](https://anaconda.org/conda-forge/scikit-dsp-comm/badges/version.svg)](https://anaconda.org/conda-forge/scikit-dsp-comm) 7 | [![Docs](https://readthedocs.org/projects/scikit-dsp-comm/badge/?version=latest)](http://scikit-dsp-comm.readthedocs.io/en/latest/?badge=latest) 8 | 9 | ## Background 10 | 11 | The origin of this package comes from the writing the book Signals and Systems for Dummies, published by Wiley in 2013. The original module for this book is named `ssd.py`. In `scikit-dsp-comm` this module is renamed to `sigsys.py` to better reflect the fact that signal processing and communications theory is founded in signals and systems, a traditional subject in electrical engineering curricula. 12 | 13 | ## Package High Level Overview 14 | 15 | This package is a collection of functions and classes to support signal processing and communications theory teaching and research. The foundation for this package is `scipy.signal`. The code in particular currently requires Python `>=3.7x`. 16 | 17 | 18 | **There are presently ten modules that make up scikit-dsp-comm:** 19 | 20 | 1. `sigsys.py` for basic signals and systems functions both continuous-time and discrete-time, including graphical display tools such as pole-zero plots, up-sampling and down-sampling. 21 | 22 | 2. `digitalcomm.py` for digital modulation theory components, including asynchronous resampling and variable time delay functions, both useful in advanced modem testing. 23 | 24 | 3. `synchronization.py` which contains phase-locked loop simulation functions and functions for carrier and phase synchronization of digital communications waveforms. 25 | 26 | 4. `fec_conv.py` for the generation rate one-half and one-third convolutional codes and soft decision Viterbi algorithm decoding, including soft and hard decisions, trellis and trellis-traceback display functions, and puncturing. 27 | 28 | 5. `fir_design_helper.py` which for easy design of lowpass, highpass, bandpass, and bandstop filters using the Kaiser window and equal-ripple designs, also includes a list plotting function for easily comparing magnitude, phase, and group delay frequency responses. 29 | 30 | 6. `iir_design_helper.py` which for easy design of lowpass, highpass, bandpass, and bandstop filters using scipy.signal Butterworth, Chebyshev I and II, and elliptical designs, including the use of the cascade of second-order sections (SOS) topology from scipy.signal, also includes a list plotting function for easily comparing of magnitude, phase, and group delay frequency responses. 31 | 32 | 7. `multirate.py` that encapsulate digital filters into objects for filtering, interpolation by an integer factor, and decimation by an integer factor. 33 | 34 | 8. `coeff2header.py` write `C/C++` header files for FIR and IIR filters implemented in `C/C++`, using the cascade of second-order section representation for the IIR case. This last module find use in real-time signal processing on embedded systems, but can be used for simulation models in `C/C++`. 35 | 36 | Presently the collection of modules contains about 125 functions and classes. The authors/maintainers are working to get more detailed documentation in place. 37 | 38 | 39 | ## Documentation 40 | Documentation is now housed on `readthedocs` which you can get to by clicking the docs badge near the top of this `README`. Example notebooks can be viewed on [GitHub pages](https://mwickert.github.io/scikit-dsp-comm/). In time more notebook postings will be extracted from [Dr. Wickert's Info Center](https://faculty.uccs.edu/mwickert/). 41 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-modernist -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/scikit-dsp-comm.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/scikit-dsp-comm.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/scikit-dsp-comm" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/scikit-dsp-comm" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\scikit-dsp-comm.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\scikit-dsp-comm.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=7.1.0,<8.0.0 2 | numpydoc 3 | matplotlib>=3.0.0 4 | recommonmark 5 | ipykernel 6 | ipywidgets 7 | nbsphinx 8 | jupyter_sphinx 9 | -------------------------------------------------------------------------------- /docs/source/coeff2header.rst: -------------------------------------------------------------------------------- 1 | coeff2header 2 | ============ 3 | 4 | .. automodule:: sk_dsp_comm.coeff2header 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # scikit-dsp-comm documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Jul 10 20:21:46 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | from logging import getLogger 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath('.')) 23 | sys.path.insert(0, os.path.abspath('../../src/')) 24 | log = getLogger(__name__) 25 | try: 26 | os.symlink('../../README.md', 'readme.md') 27 | except FileExistsError as fee: 28 | log.debug(fee) 29 | 30 | # -- General configuration ------------------------------------------------ 31 | 32 | # If your documentation needs a minimal Sphinx version, state it here. 33 | #needs_sphinx = '1.0' 34 | 35 | # Add any Sphinx extension module names here, as strings. They can be 36 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 37 | # ones. 38 | extensions = [ 39 | 'matplotlib.sphinxext.plot_directive', 40 | 'sphinx.ext.autodoc', 41 | 'sphinx.ext.doctest', 42 | 'sphinx.ext.intersphinx', 43 | 'sphinx.ext.coverage', 44 | 'sphinx.ext.mathjax', 45 | 'sphinx.ext.ifconfig', 46 | 'sphinx.ext.viewcode', 47 | 'numpydoc', 48 | 'sphinx.ext.autosummary', 49 | 'recommonmark', 50 | 'nbsphinx', 51 | ] 52 | 53 | 54 | # Add any paths that contain templates here, relative to this directory. 55 | templates_path = ['_templates'] 56 | 57 | # The suffix of source filenames. 58 | source_suffix = ['.rst', '.md'] 59 | 60 | # The encoding of source files. 61 | #source_encoding = 'utf-8-sig' 62 | 63 | # The master toctree document. 64 | master_doc = 'index' 65 | 66 | # General information about the project. 67 | project = u'scikit-dsp-comm' 68 | copyright = u'2017, Mark Wickert, Chiranth Siddappa' 69 | 70 | # The version info for the project you're documenting, acts as replacement for 71 | # |version| and |release|, also used in various other places throughout the 72 | # built documents. 73 | # 74 | # The short X.Y version. 75 | from sk_dsp_comm import __version__ 76 | version = __version__.__version__ 77 | # The full version, including alpha/beta/rc tags. 78 | release = __version__.__version__ 79 | 80 | # The language for content autogenerated by Sphinx. Refer to documentation 81 | # for a list of supported languages. 82 | #language = None 83 | 84 | # There are two options for replacing |today|: either, you set today to some 85 | # non-false value, then it is used: 86 | #today = '' 87 | # Else, today_fmt is used as the format for a strftime call. 88 | #today_fmt = '%B %d, %Y' 89 | 90 | # List of patterns, relative to source directory, that match files and 91 | # directories to ignore when looking for source files. 92 | exclude_patterns = ['_build', '**.ipynb_checkpoints'] 93 | 94 | # The reST default role (used for this markup: `text`) to use for all 95 | # documents. 96 | #default_role = None 97 | 98 | # If true, '()' will be appended to :func: etc. cross-reference text. 99 | #add_function_parentheses = True 100 | 101 | # If true, the current module name will be prepended to all description 102 | # unit titles (such as .. function::). 103 | #add_module_names = True 104 | 105 | # If true, sectionauthor and moduleauthor directives will be shown in the 106 | # output. They are ignored by default. 107 | show_authors = True 108 | 109 | # The name of the Pygments (syntax highlighting) style to use. 110 | pygments_style = 'sphinx' 111 | 112 | # A list of ignored prefixes for module index sorting. 113 | #modindex_common_prefix = [] 114 | 115 | # If true, keep warnings as "system message" paragraphs in the built documents. 116 | #keep_warnings = False 117 | 118 | 119 | # -- Options for HTML output ---------------------------------------------- 120 | 121 | # The theme to use for HTML and HTML Help pages. See the documentation for 122 | # a list of builtin themes. 123 | html_theme = 'alabaster' 124 | 125 | # Theme options are theme-specific and customize the look and feel of a theme 126 | # further. For a list of options available for each theme, see the 127 | # documentation. 128 | #html_theme_options = {} 129 | 130 | # Add any paths that contain custom themes here, relative to this directory. 131 | #html_theme_path = [] 132 | 133 | # The name for this set of Sphinx documents. If None, it defaults to 134 | # " v documentation". 135 | #html_title = None 136 | 137 | # A shorter title for the navigation bar. Default is the same as html_title. 138 | #html_short_title = None 139 | 140 | # The name of an image file (relative to this directory) to place at the top 141 | # of the sidebar. 142 | html_logo = 'logo.png' 143 | 144 | # The name of an image file (within the static path) to use as favicon of the 145 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 146 | # pixels large. 147 | #html_favicon = None 148 | 149 | # Add any paths that contain custom static files (such as style sheets) here, 150 | # relative to this directory. They are copied after the builtin static files, 151 | # so a file named "default.css" will overwrite the builtin "default.css". 152 | html_static_path = [] 153 | 154 | # Add any extra paths that contain custom files (such as robots.txt or 155 | # .htaccess) here, relative to this directory. These files are copied 156 | # directly to the root of the documentation. 157 | #html_extra_path = [] 158 | 159 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 160 | # using the given strftime format. 161 | html_last_updated_fmt = '%b %d, %Y' 162 | 163 | # If true, SmartyPants will be used to convert quotes and dashes to 164 | # typographically correct entities. 165 | #html_use_smartypants = True 166 | 167 | # Custom sidebar templates, maps document names to template names. 168 | #html_sidebars = {} 169 | 170 | # Additional templates that should be rendered to pages, maps page names to 171 | # template names. 172 | #html_additional_pages = {} 173 | 174 | # If false, no module index is generated. 175 | #html_domain_indices = True 176 | 177 | # If false, no index is generated. 178 | html_use_index = True 179 | 180 | # If true, the index is split into individual pages for each letter. 181 | #html_split_index = False 182 | 183 | # If true, links to the reST sources are added to the pages. 184 | #html_show_sourcelink = True 185 | 186 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 187 | #html_show_sphinx = True 188 | 189 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 190 | #html_show_copyright = True 191 | 192 | # If true, an OpenSearch description file will be output, and all pages will 193 | # contain a tag referring to it. The value of this option must be the 194 | # base URL from which the finished HTML is served. 195 | #html_use_opensearch = '' 196 | 197 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 198 | #html_file_suffix = None 199 | 200 | # Output file base name for HTML help builder. 201 | htmlhelp_basename = 'scikit-dsp-commdoc' 202 | 203 | # Mathjax 204 | #mathjax_path = "scipy-mathjax/MathJax.js?config=scipy-mathjax" 205 | 206 | # -- Options for LaTeX output --------------------------------------------- 207 | 208 | latex_elements = { 209 | # The paper size ('letterpaper' or 'a4paper'). 210 | #'papersize': 'letterpaper', 211 | 212 | # The font size ('10pt', '11pt' or '12pt'). 213 | #'pointsize': '10pt', 214 | 215 | # Additional stuff for the LaTeX preamble. 216 | #'preamble': '', 217 | } 218 | 219 | # Grouping the document tree into LaTeX files. List of tuples 220 | # (source start file, target name, title, 221 | # author, documentclass [howto, manual, or own class]). 222 | latex_documents = [ 223 | ('index', 'scikit-dsp-comm.tex', u'scikit-dsp-comm Documentation', 224 | u'Mark Wickert, Chiranth Siddappa', 'manual'), 225 | ] 226 | 227 | # The name of an image file (relative to this directory) to place at the top of 228 | # the title page. 229 | #latex_logo = None 230 | 231 | # For "manual" documents, if this is true, then toplevel headings are parts, 232 | # not chapters. 233 | #latex_use_parts = False 234 | 235 | # If true, show page references after internal links. 236 | #latex_show_pagerefs = False 237 | 238 | # If true, show URL addresses after external links. 239 | #latex_show_urls = False 240 | 241 | # Documents to append as an appendix to all manuals. 242 | #latex_appendices = [] 243 | 244 | # If false, no module index is generated. 245 | #latex_domain_indices = True 246 | 247 | 248 | # -- Options for manual page output --------------------------------------- 249 | 250 | # One entry per manual page. List of tuples 251 | # (source start file, name, description, authors, manual section). 252 | man_pages = [ 253 | ('index', 'scikit-dsp-comm', u'scikit-dsp-comm Documentation', 254 | [u'Mark Wickert, Chiranth Siddappa'], 1) 255 | ] 256 | 257 | # If true, show URL addresses after external links. 258 | #man_show_urls = False 259 | 260 | 261 | # -- Options for Texinfo output ------------------------------------------- 262 | 263 | # Grouping the document tree into Texinfo files. List of tuples 264 | # (source start file, target name, title, author, 265 | # dir menu entry, description, category) 266 | texinfo_documents = [ 267 | ('index', 'scikit-dsp-comm', u'scikit-dsp-comm Documentation', 268 | u'Mark Wickert, Chiranth Siddappa', 'scikit-dsp-comm', 'One line description of project.', 269 | 'Miscellaneous'), 270 | ] 271 | 272 | # Documents to append as an appendix to all manuals. 273 | #texinfo_appendices = [] 274 | 275 | # If false, no module index is generated. 276 | #texinfo_domain_indices = True 277 | 278 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 279 | #texinfo_show_urls = 'footnote' 280 | 281 | # If true, do not generate a @detailmenu in the "Top" node's menu. 282 | #texinfo_no_detailmenu = False 283 | 284 | 285 | # Example configuration for intersphinx: refer to the Python standard library. 286 | intersphinx_mapping = {'http://docs.python.org/': None} 287 | 288 | # Plotting 289 | 290 | # Generate plots for example sections 291 | numpydoc_use_plots = True 292 | 293 | plot_pre_code = """ 294 | import numpy as np 295 | np.random.seed(100) 296 | """ 297 | plot_include_source = True 298 | plot_formats = [('png', 96), 'pdf'] 299 | plot_html_show_formats = False 300 | 301 | import math 302 | phi = (math.sqrt(5) + 1)/2 303 | 304 | font_size = 13*72/96.0 # 13 px 305 | 306 | plot_rcparams = { 307 | 'font.size': font_size, 308 | 'axes.titlesize': font_size, 309 | 'axes.labelsize': font_size, 310 | 'xtick.labelsize': font_size, 311 | 'ytick.labelsize': font_size, 312 | 'legend.fontsize': font_size, 313 | 'figure.figsize': (3*phi, 3), 314 | 'figure.subplot.bottom': 0.2, 315 | 'figure.subplot.left': 0.2, 316 | 'figure.subplot.right': 0.9, 317 | 'figure.subplot.top': 0.85, 318 | 'figure.subplot.wspace': 0.4, 319 | 'text.usetex': False, 320 | } 321 | 322 | -------------------------------------------------------------------------------- /docs/source/digitalcom.rst: -------------------------------------------------------------------------------- 1 | digitalcom 2 | ========== 3 | 4 | .. automodule:: sk_dsp_comm.digitalcom 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/fec_conv.rst: -------------------------------------------------------------------------------- 1 | fec_conv 2 | ======== 3 | 4 | .. automodule:: sk_dsp_comm.fec_conv 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/fir_design_helper.rst: -------------------------------------------------------------------------------- 1 | fir_design_helper 2 | ================= 3 | 4 | .. automodule:: sk_dsp_comm.fir_design_helper 5 | :members: 6 | 7 | -------------------------------------------------------------------------------- /docs/source/iir_design_helper.rst: -------------------------------------------------------------------------------- 1 | iir_design_helper 2 | ================= 3 | 4 | .. automodule:: sk_dsp_comm.iir_design_helper 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. scikit-dsp-comm documentation master file, created by 2 | sphinx-quickstart on Mon Jul 10 20:21:46 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to scikit-dsp-comm's documentation! 7 | =========================================== 8 | 9 | Readme 10 | ------ 11 | 12 | .. toctree:: 13 | readme.md 14 | 15 | Examples 16 | -------- 17 | 18 | * `SciPy 2017 Tutorial `_ 19 | 20 | .. toctree:: 21 | :titlesonly: 22 | 23 | nb_examples 24 | 25 | .. toctree:: 26 | :maxdepth: 2 27 | :caption: Modules 28 | 29 | coeff2header 30 | digitalcom 31 | fec_conv 32 | fir_design_helper 33 | iir_design_helper 34 | multirate_helper 35 | sigsys 36 | synchronization 37 | 38 | Indices and tables 39 | ------------------ 40 | 41 | * :ref:`genindex` 42 | * :ref:`modindex` 43 | * :ref:`search` 44 | -------------------------------------------------------------------------------- /docs/source/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/logo.png -------------------------------------------------------------------------------- /docs/source/multirate_helper.rst: -------------------------------------------------------------------------------- 1 | multirate_helper 2 | ================ 3 | 4 | .. automodule:: sk_dsp_comm.multirate_helper 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/nb_examples.rst: -------------------------------------------------------------------------------- 1 | Jupyter Notebook Examples 2 | ========================= 3 | 4 | .. toctree:: 5 | :caption: Continuous-Time Signals and Systems using sigsys 6 | 7 | ../nb_examples/Continuous-Time Signals and Systems using sigsys 8 | 9 | .. toctree:: 10 | :caption: FIR and IIR Filter Design 11 | 12 | ../nb_examples/FIR_and_IIR_Filter_Design 13 | 14 | .. toctree:: 15 | :caption: Multirate Processing 16 | 17 | ../nb_examples/Multirate_Processing.ipynb 18 | 19 | .. toctree:: 20 | :caption: Convolutional Codes 21 | 22 | ../nb_examples/Convolutional_Codes 23 | 24 | .. toctree:: 25 | :caption: Block Codes 26 | 27 | ../nb_examples/Block_Codes -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/Block_Codes.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/Block_Codes.PNG -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/Cyclic_Decoder.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/Cyclic_Decoder.PNG -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/Cyclic_Encoder.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/Cyclic_Encoder.PNG -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/Decimator_Top_Level@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/Decimator_Top_Level@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/FIR_Kaiser_Equiripple_Table@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/FIR_Kaiser_Equiripple_Table@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/FIR_Lowpass_Highpass_Bandpass_Bandstop@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/FIR_Lowpass_Highpass_Bandpass_Bandstop@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/IIR_Lowpass_Highpass_Bandpass_Bandstop@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/IIR_Lowpass_Highpass_Bandpass_Bandstop@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/IIR_Table@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/IIR_Table@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/Interactive_FM_Rx@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/Interactive_FM_Rx@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/Interpolator_Top_Level@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/Interpolator_Top_Level@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/Jupyter_concurrent_tasks@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/Jupyter_concurrent_tasks@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/Multirate_Table1@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/Multirate_Table1@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/Multirate_Table2@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/Multirate_Table2@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/Multirate_Table3@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/Multirate_Table3@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/Probe_Locations@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/Probe_Locations@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/300ppi/RTLSDR_Streaming_Block@300ppi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/300ppi/RTLSDR_Streaming_Block@300ppi.png -------------------------------------------------------------------------------- /docs/source/nb_examples/Multirate_Processing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%pylab inline\n", 10 | "import sk_dsp_comm.sigsys as ss\n", 11 | "import sk_dsp_comm.fir_design_helper as fir_d\n", 12 | "import sk_dsp_comm.iir_design_helper as iir_d\n", 13 | "import sk_dsp_comm.multirate_helper as mrh\n", 14 | "import scipy.signal as signal\n", 15 | "from IPython.display import Audio, display\n", 16 | "from IPython.display import Image, SVG" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "%config InlineBackend.figure_formats=['svg'] # SVG inline viewing" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "# Multirate Signal Processing Using `multirate_helper`\n", 33 | "In this section the classes `multirate_FIR` and `multirate_IIR`, found in the module `sk_dsp_comm.multirate_helper`, are discussed with the aim of seeing how they can be used to filter, interpolate (upsample and filter), and decimate (filter and downsample) discrete time signals. Fundamentally the processing consists of two elements: (1) and upsampler or downsampler and (2) a lowpass filter. \n", 34 | "\n", 35 | "Fundamentally this modules provides classes to change the sampling rate by an integer factor, either up, *interpolation* or down, *decimation*, with integrated filtering to supress spectral images or aliases, respectively. The top level block diagram of the interpolator and decimator are given in the following two figures. The frequencies given in the figures assume that the interpolator is rate chainging from 8 ksps to 96 ksps ($L=12$) and the decimator is rate changing from 96 ksps to 8 ksps ($M=12$). This is for example purposes only. The FIR/IIR filter cutoff frequency will in general be $f_c = f_\\text{s,out}/(2L)$ for the decimator and $f_c = f_\\text{s,in}/(2M)$. The primitives to implement the classes are available in `sk_dsp_comm.sigsys` and `scipy.signal`." 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "metadata": {}, 42 | "outputs": [], 43 | "source": [ 44 | "Image('300ppi/Interpolator_Top_Level@300ppi.png',width='60%')" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": null, 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "Image('300ppi/Decimator_Top_Level@300ppi.png',width='60%')" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "The upsample block, shown above with arrow pointing up and integer $L=12$ next to the arrow, takes the input sequence and produces the output sequence by inserting $L-1$ (as shown here 11) zero samples between each input sample. The downsample block, shown above with arrow pointing down and integer $M=12$ next to the arrow, takes the input sequence and retains at the output sequence every $M$th (as shown here 12th) sample.\n", 61 | "\n", 62 | "The impact of these blocks in the frequency domain is a little harder to explain. In words, the spectrum at the output of the upsampler is compressed by the factor $L$, such that it will contain $L$ spectral images, including the fundamental image centered at $f = 0$, evenly spaced up to the sampling $f_s$. Overall the spectrum of $x_\\text{up}[n]$ is of course periodic with respect to the sampling rate. The lowpass filter interpolates signal sample values from the non-zero samples where the zero samples reside. It is this interpolation that effectively removed or suppresses the spectral images outside the interval $|f| > f_s/(2L)$.\n", 63 | "\n", 64 | "For the downsampler the input spectrum is stretched along the frequency axis by the factor $M$, with aliasing from frequency bands outside $|f| < f_s/(2M)$. To avoid aliasing the lowpass filter blocks input signals for $f > f_s/(2M)$.\n", 65 | "\n", 66 | "To get started using the module you will need an `import` similar to:\n", 67 | "\n", 68 | "```python\n", 69 | "import sk_dsp_comm.multirate_helper as mrh\n", 70 | "```\n", 71 | "\n", 72 | "## The `rate_change` Class\n", 73 | "We start with the description of a third class, `mrh.rate_change`, which is simplistic, offering little user interaction, but automatically designs the required lowpass filter you see in the above block diagrams. Below is a table which describes this class:" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "Image('300ppi/Multirate_Table1@300ppi.png',width='85%')" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "This class is used in the analog modulation demos for the [ECE 4625/5625 Chapter 3 Jupyter notebook](http://www.eas.uccs.edu/~mwickert/ece5625/lecture_notes/5625_Chapter_3_IPYNB.zip). Using this class you can quickly create a interpolation or decimation block with the necessary lowpass filter automatically designed and implemented. Fine tuning of the filter is limited to choosing the filter order and the cutoff frequency as a fraction of the signal bandwidth given the rate change integer, $L$ or $M$. The filter type is also limited to Butterworth or Chebyshev type 1 having passband ripple of 0.05 dB.\n", 90 | "\n", 91 | "## A Simple Example\n", 92 | "Pass a sinusoidal signal through an $L=4$ interpolator. Verify that spectral images occur with the use of the interpolation lowpass filter." 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "fs_in = 8000\n", 102 | "M = 4\n", 103 | "fs_out = M*fs_in\n", 104 | "rc1 = mrh.rate_change(M) # Rate change by 4\n", 105 | "n = arange(0,1000)\n", 106 | "x = cos(2*pi*1000/fs_in*n)\n", 107 | "x_up = ss.upsample(x,4)\n", 108 | "y = rc1.up(x)" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "### Time Domain" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "subplot(211)\n", 125 | "stem(n[500:550],x_up[500:550]);\n", 126 | "ylabel(r'$x_{up}[n]$')\n", 127 | "title(r'Upsample by $L=4$ Output')\n", 128 | "#ylim(-100,-10)\n", 129 | "subplot(212)\n", 130 | "stem(n[500:550],y[500:550]);\n", 131 | "ylabel(r'$y[n]$')\n", 132 | "xlabel(r'')\n", 133 | "title(r'Interpolate by $L=4$ Output')\n", 134 | "#ylim(-100,-10)\n", 135 | "tight_layout()" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "* Clearly the lowpass interpolation filter has done a good job of filling in values for the zero samples" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "### Frequency Domain" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": null, 155 | "metadata": {}, 156 | "outputs": [], 157 | "source": [ 158 | "subplot(211)\n", 159 | "psd(x_up,2**10,fs_out);\n", 160 | "ylabel(r'PSD (dB)')\n", 161 | "title(r'Upsample by $L=4$ Output')\n", 162 | "ylim(-100,-10)\n", 163 | "subplot(212)\n", 164 | "psd(y,2**10,fs_out);\n", 165 | "ylabel(r'PSD (dB)')\n", 166 | "title(r'Interpolate by $L=4$ Output')\n", 167 | "ylim(-100,-10)\n", 168 | "tight_layout()" 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "metadata": {}, 174 | "source": [ 175 | "* The filtering action of the LPF does its best to suppress the images at 7000, 9000, and 15000 Hz." 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "## The `multirate_FIR` Class\n", 183 | "With this class you implement an object that can filter, interpolate, or decimate a signal. Additionally support methods drill into the characteristics of the lowpass filter at the heart of the processing block. To use this class the user must supply FIR filter coefficients that implement a lowpass filter with cutoff frequency appropriate for the desired interpolation of decimation factor. The module `sk_dsp_com.FIR_design_helper` is capable of delivering the need filter coefficients array. See [FIR design helper notes](https://mwickert.github.io/scikit-dsp-comm/example_notebooks/FIR_IIR_design_helper/FIR_and_IIR_Filter_Design.html) for multirate filter design examples.\n", 184 | "\n", 185 | "With FIR coefficients in hand it is an easy matter to create an multirate FIR object capable of filtering, interpolation, or decimation. The details of the class interface are given in Table 2 below." 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": null, 191 | "metadata": {}, 192 | "outputs": [], 193 | "source": [ 194 | "Image('300ppi/Multirate_Table2@300ppi.png',width='85%')" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "metadata": {}, 200 | "source": [ 201 | "Notice that the class also provides a means to obtain frequency response plots and pole-zero plots directly from the instantiated multirate objects.\n", 202 | "\n", 203 | "## FIR Interpolator Design Example\n", 204 | "Here we take the earlier lowpass filter designed to interpolate a signal being upsampled from $f_{s1} = 8000$ kHz to $f_{s2} = 96$ kHz. The upsampling factor is $L = f_{s2}/f_{s1} = 12$. The ideal interpolation filter should cutoff at $f_{s1}/2 = f_{s2}/(2\\cdot 12) = 8000/2 = 4000$ Hz.\n", 205 | "\n", 206 | "Recall the upsampler (`y = ss.upsampler(x, L)`) inserts $L-1$ samples between each input sample. In the frequency domain the zero insertion replicates the input spectrum on $[0,f_{s1}/2]$ $L$ times over the interval $[0,f_{s2}]$ (equivalently $L/2$ times on the inteval $[0f_{s2}/2]$. The lowpass interpolation filter serves to removes the images above $f_{s2}/(2L)$ in the frequency domain and in so doing filling in the zeros samples with waveform interpolants in the time domain." 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": null, 212 | "metadata": {}, 213 | "outputs": [], 214 | "source": [ 215 | "# Design the filter core for an interpolator used in changing the sampling rate from 8000 Hz\n", 216 | "# to 96000 Hz\n", 217 | "b_up = fir_d.fir_remez_lpf(3300,4300,0.5,60,96000)\n", 218 | "# Create the multirate object\n", 219 | "mrh_up = mrh.multirate_FIR(b_up)" 220 | ] 221 | }, 222 | { 223 | "cell_type": "markdown", 224 | "metadata": {}, 225 | "source": [ 226 | "As an input consider a sinusoid at 1 kHz and observe the interpolator output spectrum compared with the input spectrum." 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": null, 232 | "metadata": {}, 233 | "outputs": [], 234 | "source": [ 235 | "# Sinusoidal test signal\n", 236 | "n = arange(10000)\n", 237 | "x = cos(2*pi*1000/8000*n)\n", 238 | "# Interpolate by 12 (upsample by 12 followed by lowpass filter)\n", 239 | "y = mrh_up.up(x,12)" 240 | ] 241 | }, 242 | { 243 | "cell_type": "code", 244 | "execution_count": null, 245 | "metadata": {}, 246 | "outputs": [], 247 | "source": [ 248 | "# Plot the results\n", 249 | "subplot(211)\n", 250 | "psd(x,2**12,8000);\n", 251 | "title(r'1 KHz Sinusoid Input to $L=12$ Interpolator')\n", 252 | "ylabel(r'PSD (dB)')\n", 253 | "ylim([-100,0])\n", 254 | "subplot(212)\n", 255 | "psd(y,2**12,12*8000)\n", 256 | "title(r'1 KHz Sinusoid Output from $L=12$ Interpolator')\n", 257 | "ylabel(r'PSD (dB)')\n", 258 | "ylim([-100,0])\n", 259 | "tight_layout()" 260 | ] 261 | }, 262 | { 263 | "cell_type": "markdown", 264 | "metadata": {}, 265 | "source": [ 266 | " In the above spectrum plots notice that images of the input 1 kHz sinusoid are down $\\simeq 60$ dB, which is precisely the stop band attenuation provided by the interpolation filter. The variation is due to the stopband ripple." 267 | ] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "metadata": {}, 272 | "source": [ 273 | "## The `multirate_IIR` Class\n", 274 | "With this class, as with `multirate_FIR` you implement an object that can filter, interpolate, or decimate a signal. The filter in this case is a user supplied IIR filter in second-order sections (`sos`) form. Additionally support methods drill into the characteristics of the lowpass filter at the heart of the procssing block. The module `sk_dsp_com.IIR_design_helper` is capable of delivering the need filter coefficients array. See [IIR design helper notes](https://mwickert.github.io/scikit-dsp-comm/example_notebooks/FIR_IIR_design_helper/FIR_and_IIR_Filter_Design.html) for multirate filter design examples.\n", 275 | "\n", 276 | "With IIR coefficients in hand it is an easy matter to create an multirate IIR object capable of filtering, interpolation, or decimation. The details of the class interface are given in Table 3 below." 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": null, 282 | "metadata": {}, 283 | "outputs": [], 284 | "source": [ 285 | "Image('300ppi/Multirate_Table3@300ppi.png',width='85%')" 286 | ] 287 | }, 288 | { 289 | "cell_type": "markdown", 290 | "metadata": {}, 291 | "source": [ 292 | "## IIR Decimator Design Example\n", 293 | "Whan a signal is decimated the signal is first lowpass filtered then downsampled. The lowpass filter serves to prevent aliasing as the sampling rate is reduced. Downsampling by $M$ (`y = ss.downsample(x, M)`) removes $M-1$ sampling for every $M$ sampling input or equivalently retains one sample out of $M$. The lowpass prefilter has cutoff frequency equal to the folding frequency of the output sampling rate, i.e., $f_c = f_{s2}/2$. Note avoid confusion with the project requirements, where the decimator is needed to take a rate $f_{s2}$ signal back to $f_{s1}$, let the input sampling rate be $f_{s2} = 96000$ HZ and the output sampling rate be $f_{s1} = 8000$ Hz. The input sampling rate is $M$ times the output rate, i.e., $f_{s2} = Mf_{s1}$, so you design the lowpass filter to have cutoff $f_c = f_{s2}/(2\\cdot L)$.\n", 294 | "\n", 295 | "**ECE 5625 Important Observation**: In the coherent SSB demodulator of Project 1, the decimator can be conveniently integrated with the lowpass filter that serves to remove the double frequency term.\n", 296 | "\n", 297 | "In the example that follows a Chebyshev type 1 lowpass filter is designed to have cutoff around 4000 Hz. A sinusoid is used as a test input signal at sampling rate 96000 Hz." 298 | ] 299 | }, 300 | { 301 | "cell_type": "code", 302 | "execution_count": null, 303 | "metadata": {}, 304 | "outputs": [], 305 | "source": [ 306 | "# Design the filter core for a decimator used in changing the \n", 307 | "# sampling rate from 96000 Hz to 8000 Hz\n", 308 | "b_dn, a_dn, sos_dn = iir_d.IIR_lpf(3300,4300,0.5,60,96000,'cheby1')\n", 309 | "# Create the multirate object\n", 310 | "mrh_dn = mrh.multirate_IIR(sos_dn)\n", 311 | "mrh_dn.freq_resp('dB',96000)\n", 312 | "title(r'Decimation Filter Frequency Response - Magnitude');" 313 | ] 314 | }, 315 | { 316 | "cell_type": "markdown", 317 | "metadata": {}, 318 | "source": [ 319 | "* Note the Chebyshev lowpass filter design above is very efficient compared with the 196-tap FIR lowpass designed for use in the interpolator. It is perhaps a better overall choice. The FIR has linear phase and the IIR filter does not, but for the project this is not really an issue.\n", 320 | "\n", 321 | "As an input consider a sinusoid at 1 kHz and observe the interpolator output spectrum compared with the input spectrum." 322 | ] 323 | }, 324 | { 325 | "cell_type": "code", 326 | "execution_count": null, 327 | "metadata": {}, 328 | "outputs": [], 329 | "source": [ 330 | "# Sinusoidal test signal\n", 331 | "n = arange(100000)\n", 332 | "x = cos(2*pi*1000/96000*n)\n", 333 | "# Decimate by 12 (lowpass filter followed by downsample by 12)\n", 334 | "y = mrh_dn.dn(x,12)" 335 | ] 336 | }, 337 | { 338 | "cell_type": "code", 339 | "execution_count": null, 340 | "metadata": {}, 341 | "outputs": [], 342 | "source": [ 343 | "# Plot the results\n", 344 | "subplot(211)\n", 345 | "psd(x,2**12,96000);\n", 346 | "title(r'1 KHz Sinusoid Input to $M=12$ Decimator')\n", 347 | "ylabel(r'PSD (dB)')\n", 348 | "ylim([-100,0])\n", 349 | "subplot(212)\n", 350 | "psd(y,2**12,8000)\n", 351 | "title(r'1 KHz Sinusoid Output from $M=12$ Decimator')\n", 352 | "ylabel(r'PSD (dB)')\n", 353 | "ylim([-100,0])\n", 354 | "tight_layout()" 355 | ] 356 | } 357 | ], 358 | "metadata": { 359 | "kernelspec": { 360 | "display_name": "Python 3", 361 | "language": "python", 362 | "name": "python3" 363 | }, 364 | "language_info": { 365 | "codemirror_mode": { 366 | "name": "ipython", 367 | "version": 3 368 | }, 369 | "file_extension": ".py", 370 | "mimetype": "text/x-python", 371 | "name": "python", 372 | "nbconvert_exporter": "python", 373 | "pygments_lexer": "ipython3", 374 | "version": "3.6.1" 375 | } 376 | }, 377 | "nbformat": 4, 378 | "nbformat_minor": 1 379 | } 380 | -------------------------------------------------------------------------------- /docs/source/nb_examples/audio_files/Music_Test.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/audio_files/Music_Test.wav -------------------------------------------------------------------------------- /docs/source/nb_examples/audio_files/cross_panning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/audio_files/cross_panning.png -------------------------------------------------------------------------------- /docs/source/nb_examples/audio_files/left_right_gain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/audio_files/left_right_gain.png -------------------------------------------------------------------------------- /docs/source/nb_examples/audio_files/music_buffer_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/audio_files/music_buffer_plot.png -------------------------------------------------------------------------------- /docs/source/nb_examples/audio_files/pyaudio_dsp_IO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/audio_files/pyaudio_dsp_IO.png -------------------------------------------------------------------------------- /docs/source/nb_examples/audio_files/start_stop_stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/audio_files/start_stop_stream.png -------------------------------------------------------------------------------- /docs/source/nb_examples/audio_files/three_band_widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/docs/source/nb_examples/audio_files/three_band_widgets.png -------------------------------------------------------------------------------- /docs/source/sigsys.rst: -------------------------------------------------------------------------------- 1 | sigsys 2 | ====== 3 | 4 | .. automodule:: sk_dsp_comm.sigsys 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/synchronization.rst: -------------------------------------------------------------------------------- 1 | synchronization 2 | =============== 3 | 4 | .. automodule:: sk_dsp_comm.synchronization 5 | :members: 6 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/logo.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.20.0 2 | matplotlib>=3.0.0 3 | scipy>=1.1.0 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_files = LICENSE.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import os 3 | import codecs 4 | 5 | here = os.path.abspath(os.path.dirname(__file__)) 6 | 7 | 8 | def fpath(name): 9 | return os.path.join(os.path.dirname(__file__), name) 10 | 11 | 12 | def read(fname): 13 | return codecs.open(fpath(fname), encoding='utf-8').read() 14 | 15 | 16 | requirements = read(fpath('requirements.txt')) 17 | 18 | with open("README.md", "r") as fh: 19 | long_description = fh.read() 20 | 21 | about = {} 22 | with codecs.open(os.path.join(here, 'src', 'sk_dsp_comm', '__version__.py'), encoding='utf-8') as f: 23 | exec(f.read(), about) 24 | 25 | setup(name='scikit-dsp-comm', 26 | version=about['__version__'], 27 | description='DSP and Comm package.', 28 | long_description=long_description, 29 | long_description_content_type="text/markdown", 30 | author='Mark Wickert', 31 | author_email='mwickert@uccs.edu', 32 | url='https://github.com/mwickert/scikit-dsp-comm', 33 | package_data={'sk_dsp_comm': ['ca1thru37.txt']}, 34 | include_package_data=True, 35 | license='BSD', 36 | install_requires=requirements.split(), 37 | test_suite='pytest', 38 | tests_require=['pytest','numpy', 'tox'], 39 | extras_require={ 40 | 'helpers': ['colorama', 'pyaudio', 'ipywidgets'] 41 | }, 42 | python_requires = '>=3.7', 43 | ) 44 | -------------------------------------------------------------------------------- /src/sk_dsp_comm/__init__.py: -------------------------------------------------------------------------------- 1 | from . import coeff2header 2 | from . import digitalcom 3 | from . import fec_conv 4 | from . import fir_design_helper 5 | from . import iir_design_helper 6 | from . import multirate_helper 7 | from . import sigsys 8 | from . import synchronization 9 | -------------------------------------------------------------------------------- /src/sk_dsp_comm/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = '2.0.5' 2 | -------------------------------------------------------------------------------- /src/sk_dsp_comm/coeff2header.py: -------------------------------------------------------------------------------- 1 | """ 2 | Digital Filter Coefficient Conversion to C Header Files 3 | 4 | Copyright (c) March 2017, Mark Wickert 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | The views and conclusions contained in the software and documentation are those 28 | of the authors and should not be interpreted as representing official policies, 29 | either expressed or implied, of the FreeBSD Project. 30 | """ 31 | 32 | import numpy as np 33 | import scipy.signal as signal 34 | import matplotlib.pyplot as plt 35 | from matplotlib import pylab 36 | from numpy import int16, rint, loadtxt 37 | import os 38 | from logging import getLogger 39 | log = getLogger(__name__) 40 | 41 | 42 | def fir_header(fname_out, h): 43 | """ 44 | Write FIR Filter Header Files 45 | 46 | Mark Wickert February 2015 47 | """ 48 | M = len(h) 49 | N = 3 # Coefficients per line 50 | f = open(fname_out, 'wt') 51 | f.write('//define a FIR coefficient Array\n\n') 52 | f.write('#include \n\n') 53 | f.write('#ifndef M_FIR\n') 54 | f.write('#define M_FIR %d\n' % M) 55 | f.write('#endif\n') 56 | f.write('/************************************************************************/\n'); 57 | f.write('/* FIR Filter Coefficients */\n'); 58 | f.write('float32_t h_FIR[M_FIR] = {') 59 | kk = 0; 60 | for k in range(M): 61 | # k_mod = k % M 62 | if (kk < N - 1) and (k < M - 1): 63 | f.write('%15.12f,' % h[k]) 64 | kk += 1 65 | elif (kk == N - 1) & (k < M - 1): 66 | f.write('%15.12f,\n' % h[k]) 67 | if k < M: 68 | f.write(' ') 69 | kk = 0 70 | else: 71 | f.write('%15.12f' % h[k]) 72 | f.write('};\n') 73 | f.write('/************************************************************************/\n') 74 | f.close() 75 | 76 | 77 | def fir_fix_header(fname_out, h): 78 | """ 79 | Write FIR Fixed-Point Filter Header Files 80 | 81 | Mark Wickert February 2015 82 | """ 83 | M = len(h) 84 | hq = int16(rint(h * 2 ** 15)) 85 | N = 8 # Coefficients per line 86 | f = open(fname_out, 'wt') 87 | f.write('//define a FIR coefficient Array\n\n') 88 | f.write('#include \n\n') 89 | f.write('#ifndef M_FIR\n') 90 | f.write('#define M_FIR %d\n' % M) 91 | f.write('#endif\n') 92 | f.write('/************************************************************************/\n'); 93 | f.write('/* FIR Filter Coefficients */\n'); 94 | f.write('int16_t h_FIR[M_FIR] = {') 95 | kk = 0; 96 | for k in range(M): 97 | # k_mod = k % M 98 | if (kk < N - 1) and (k < M - 1): 99 | f.write('%5d,' % hq[k]) 100 | kk += 1 101 | elif (kk == N - 1) & (k < M - 1): 102 | f.write('%5d,\n' % hq[k]) 103 | if k < M: 104 | f.write(' ') 105 | kk = 0 106 | else: 107 | f.write('%5d' % hq[k]) 108 | f.write('};\n') 109 | f.write('/************************************************************************/\n') 110 | f.close() 111 | 112 | 113 | def iir_sos_header(fname_out, SOS_mat): 114 | """ 115 | Write IIR SOS Header Files 116 | File format is compatible with CMSIS-DSP IIR 117 | Directform II Filter Functions 118 | 119 | Mark Wickert March 2015-October 2016 120 | """ 121 | Ns, Mcol = SOS_mat.shape 122 | f = open(fname_out, 'wt') 123 | f.write('//define a IIR SOS CMSIS-DSP coefficient array\n\n') 124 | f.write('#include \n\n') 125 | f.write('#ifndef STAGES\n') 126 | f.write('#define STAGES %d\n' % Ns) 127 | f.write('#endif\n') 128 | f.write('/*********************************************************/\n'); 129 | f.write('/* IIR SOS Filter Coefficients */\n'); 130 | f.write('float32_t ba_coeff[%d] = { //b0,b1,b2,a1,a2,... by stage\n' % (5 * Ns)) 131 | for k in range(Ns): 132 | if (k < Ns - 1): 133 | f.write(' %+-13e, %+-13e, %+-13e,\n' % \ 134 | (SOS_mat[k, 0], SOS_mat[k, 1], SOS_mat[k, 2])) 135 | f.write(' %+-13e, %+-13e,\n' % \ 136 | (-SOS_mat[k, 4], -SOS_mat[k, 5])) 137 | else: 138 | f.write(' %+-13e, %+-13e, %+-13e,\n' % \ 139 | (SOS_mat[k, 0], SOS_mat[k, 1], SOS_mat[k, 2])) 140 | f.write(' %+-13e, %+-13e\n' % \ 141 | (-SOS_mat[k, 4], -SOS_mat[k, 5])) 142 | # for k in range(Ns): 143 | # if (k < Ns-1): 144 | # f.write(' %15.12f, %15.12f, %15.12f,\n' % \ 145 | # (SOS_mat[k,0],SOS_mat[k,1],SOS_mat[k,2])) 146 | # f.write(' %15.12f, %15.12f,\n' % \ 147 | # (-SOS_mat[k,4],-SOS_mat[k,5])) 148 | # else: 149 | # f.write(' %15.12f, %15.12f, %15.12f,\n' % \ 150 | # (SOS_mat[k,0],SOS_mat[k,1],SOS_mat[k,2])) 151 | # f.write(' %15.12f, %15.12f\n' % \ 152 | # (-SOS_mat[k,4],-SOS_mat[k,5])) 153 | f.write('};\n') 154 | f.write('/*********************************************************/\n') 155 | f.close() 156 | 157 | 158 | def freqz_resp_list(b, a=np.array([1]), mode='dB', fs=1.0, n_pts=1024, fsize=(6, 4)): 159 | """ 160 | A method for displaying digital filter frequency response magnitude, 161 | phase, and group delay. A plot is produced using matplotlib 162 | 163 | freq_resp(self,mode = 'dB',Npts = 1024) 164 | 165 | A method for displaying the filter frequency response magnitude, 166 | phase, and group delay. A plot is produced using matplotlib 167 | 168 | freqz_resp(b,a=[1],mode = 'dB',Npts = 1024,fsize=(6,4)) 169 | 170 | Parameters 171 | ---------- 172 | b : ndarray of numerator coefficients 173 | a : ndarray of denominator coefficents 174 | mode : display mode: 'dB' magnitude, 'phase' in radians, or 175 | 'groupdelay_s' in samples and 'groupdelay_t' in sec, 176 | all versus frequency in Hz 177 | n_pts : number of points to plot; default is 1024 178 | fsize : figure size; defult is (6,4) inches 179 | 180 | Mark Wickert, January 2015 181 | """ 182 | if type(b) == list: 183 | # We have a list of filters 184 | N_filt = len(b) 185 | else: 186 | return None 187 | f = np.arange(0, n_pts) / (2.0 * n_pts) 188 | for n in range(N_filt): 189 | w, H = signal.freqz(b[n], a[n], 2 * np.pi * f) 190 | if n == 0: 191 | plt.figure(figsize=fsize) 192 | if mode.lower() == 'db': 193 | plt.plot(f * fs, 20 * np.log10(np.abs(H))) 194 | if n == N_filt - 1: 195 | plt.xlabel('Frequency (Hz)') 196 | plt.ylabel('Gain (dB)') 197 | plt.title('Frequency Response - Magnitude') 198 | 199 | elif mode.lower() == 'phase': 200 | plt.plot(f * fs, np.angle(H)) 201 | if n == N_filt - 1: 202 | plt.xlabel('Frequency (Hz)') 203 | plt.ylabel('Phase (rad)') 204 | plt.title('Frequency Response - Phase') 205 | 206 | elif (mode.lower() == 'groupdelay_s') or (mode.lower() == 'groupdelay_t'): 207 | """ 208 | Notes 209 | ----- 210 | 211 | Since this calculation involves finding the derivative of the 212 | phase response, care must be taken at phase wrapping points 213 | and when the phase jumps by +/-pi, which occurs when the 214 | amplitude response changes sign. Since the amplitude response 215 | is zero when the sign changes, the jumps do not alter the group 216 | delay results. 217 | """ 218 | theta = np.unwrap(np.angle(H)) 219 | # Since theta for an FIR filter is likely to have many pi phase 220 | # jumps too, we unwrap a second time 2*theta and divide by 2 221 | theta2 = np.unwrap(2 * theta) / 2. 222 | theta_dif = np.diff(theta2) 223 | f_diff = np.diff(f) 224 | Tg = -np.diff(theta2) / np.diff(w) 225 | # For gain almost zero set groupdelay = 0 226 | idx = pylab.find(20 * np.log10(H[:-1]) < -400) 227 | Tg[idx] = np.zeros(len(idx)) 228 | max_Tg = np.max(Tg) 229 | # print(max_Tg) 230 | if mode.lower() == 'groupdelay_t': 231 | max_Tg /= fs 232 | plt.plot(f[:-1] * fs, Tg / fs) 233 | plt.ylim([0, 1.2 * max_Tg]) 234 | else: 235 | plt.plot(f[:-1] * fs, Tg) 236 | plt.ylim([0, 1.2 * max_Tg]) 237 | if n == N_filt - 1: 238 | plt.xlabel('Frequency (Hz)') 239 | if mode.lower() == 'groupdelay_t': 240 | plt.ylabel('Group Delay (s)') 241 | else: 242 | plt.ylabel('Group Delay (samples)') 243 | plt.title('Frequency Response - Group Delay') 244 | else: 245 | s1 = 'Error, mode must be "dB", "phase, ' 246 | s2 = '"groupdelay_s", or "groupdelay_t"' 247 | log.info(s1 + s2) 248 | 249 | 250 | def ca_code_header(fname_out, Nca): 251 | """ 252 | Write 1023 bit CA (Gold) Code Header Files 253 | 254 | Mark Wickert February 2015 255 | """ 256 | dir_path = os.path.dirname(os.path.realpath(__file__)) 257 | ca = loadtxt(dir_path + '/ca1thru37.txt', dtype=int16, usecols=(Nca - 1,), unpack=True) 258 | 259 | M = 1023 # code period 260 | N = 23 # code bits per line 261 | Sca = 'ca' + str(Nca) 262 | f = open(fname_out, 'wt') 263 | f.write('//define a CA code\n\n') 264 | f.write('#include \n\n') 265 | f.write('#ifndef N_CA\n') 266 | f.write('#define N_CA %d\n' % M) 267 | f.write('#endif\n') 268 | f.write('/*******************************************************************/\n'); 269 | f.write('/* 1023 Bit CA Gold Code %2d */\n' \ 270 | % Nca); 271 | f.write('int8_t ca%d[N_CA] = {' % Nca) 272 | kk = 0; 273 | for k in range(M): 274 | # k_mod = k % M 275 | if (kk < N - 1) and (k < M - 1): 276 | f.write('%d,' % ca[k]) 277 | kk += 1 278 | elif (kk == N - 1) & (k < M - 1): 279 | f.write('%d,\n' % ca[k]) 280 | if k < M: 281 | if Nca < 10: 282 | f.write(' ' * 20) 283 | else: 284 | f.write(' ' * 21) 285 | kk = 0 286 | else: 287 | f.write('%d' % ca[k]) 288 | f.write('};\n') 289 | f.write('/*******************************************************************/\n') 290 | f.close() 291 | -------------------------------------------------------------------------------- /src/sk_dsp_comm/fec_block.py: -------------------------------------------------------------------------------- 1 | """ 2 | Block Encoding and Decoding 3 | 4 | Copyright (c) November 2018, Mark Wickert and Andrew Smit 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | The views and conclusions contained in the software and documentation are those 28 | of the authors and should not be interpreted as representing official policies, 29 | either expressed or implied, of the FreeBSD Project. 30 | 31 | 32 | A forward error correcting coding (FEC) class which defines methods 33 | for performing convolutional encoding and decoding. Arbitrary 34 | polynomials are supported, but the rate is presently limited to r = 1/n, 35 | where n = 2. Punctured (perforated) convolutional codes are also supported. 36 | The puncturing pattern (matrix) is arbitrary. 37 | 38 | Two popular encoder polynomial sets are: 39 | 40 | K = 3 ==> G1 = '111', G2 = '101' and 41 | K = 7 ==> G1 = '1011011', G2 = '1111001'. 42 | 43 | A popular puncturing pattern to convert from rate 1/2 to rate 3/4 is 44 | a G1 output puncture pattern of '110' and a G2 output puncture 45 | pattern of '101'. 46 | 47 | Graphical display functions are included to allow the user to 48 | better understand the operation of the Viterbi decoder. 49 | 50 | Mark Wickert and Andrew Smit: October 2018. 51 | """ 52 | 53 | import numpy as np 54 | import scipy.special as special 55 | from .digitalcom import q_fctn 56 | from .fec_conv import binary 57 | from logging import getLogger 58 | log = getLogger(__name__) 59 | 60 | 61 | class FECHamming(object): 62 | """ 63 | Class responsible for creating hamming block codes and then 64 | encoding and decoding. Methods provided include hamm_gen, 65 | hamm_encoder(), hamm_decoder(). 66 | 67 | Parameters 68 | ---------- 69 | j: Hamming code order (in terms of parity bits) where n = 2^j-1, 70 | k = n-j, and the rate is k/n. 71 | 72 | Returns 73 | ------- 74 | 75 | Examples 76 | -------- 77 | 78 | Andrew Smit November 2018 79 | """ 80 | 81 | def __init__(self,j): 82 | self.j = j 83 | self.G, self.H, self.R, self.n, self.k = self.hamm_gen(self.j) 84 | log.info('(%d,%d) hamming code object' %(self.n,self.k)) 85 | 86 | def hamm_gen(self,j): 87 | """ 88 | Generates parity check matrix (H) and generator 89 | matrix (G). 90 | 91 | Parameters 92 | ---------- 93 | j: Number of Hamming code parity bits with n = 2^j-1 and k = n-j 94 | 95 | returns 96 | ------- 97 | G: Systematic generator matrix with left-side identity matrix 98 | H: Systematic parity-check matrix with right-side identity matrix 99 | R: k x k identity matrix 100 | n: number of total bits/block 101 | k: number of source bits/block 102 | 103 | Andrew Smit November 2018 104 | 105 | """ 106 | if(j < 3): 107 | raise ValueError('j must be > 2') 108 | 109 | # calculate codeword length 110 | n = 2**j-1 111 | 112 | # calculate source bit length 113 | k = n-j 114 | 115 | # Allocate memory for Matrices 116 | G = np.zeros((k,n),dtype=int) 117 | H = np.zeros((j,n),dtype=int) 118 | P = np.zeros((j,k),dtype=int) 119 | R = np.zeros((k,n),dtype=int) 120 | 121 | # Encode parity-check matrix columns with binary 1-n 122 | for i in range(1,n+1): 123 | b = list(binary(i,j)) 124 | for m in range(0,len(b)): 125 | b[m] = int(b[m]) 126 | H[:,i-1] = np.array(b) 127 | 128 | # Reformat H to be systematic 129 | H1 = np.zeros((1,j),dtype=int) 130 | H2 = np.zeros((1,j),dtype=int) 131 | for i in range(0,j): 132 | idx1 = 2**i-1 133 | idx2 = n-i-1 134 | H1[0,:] = H[:,idx1] 135 | H2[0,:] = H[:,idx2] 136 | H[:,idx1] = H2 137 | H[:,idx2] = H1 138 | 139 | # Get parity matrix from H 140 | P = H[:,:k] 141 | 142 | # Use P to calcuate generator matrix P 143 | G[:,:k] = np.diag(np.ones(k)) 144 | G[:,k:] = P.T 145 | 146 | # Get k x k identity matrix 147 | R[:,:k] = np.diag(np.ones(k)) 148 | 149 | return G, H, R, n, k 150 | 151 | def hamm_encoder(self,x): 152 | """ 153 | Encodes input bit array x using hamming block code. 154 | 155 | parameters 156 | ---------- 157 | x: array of source bits to be encoded by block encoder. 158 | 159 | returns 160 | ------- 161 | codewords: array of code words generated by generator 162 | matrix G and input x. 163 | 164 | Andrew Smit November 2018 165 | """ 166 | if(np.dtype(x[0]) != int): 167 | raise ValueError('Error: Invalid data type. Input must be a vector of ints') 168 | 169 | if(len(x) % self.k or len(x) < self.k): 170 | raise ValueError('Error: Invalid input vector length. Length must be a multiple of %d' %self.k) 171 | 172 | N_symbols = int(len(x)/self.k) 173 | codewords = np.zeros(N_symbols*self.n) 174 | x = np.reshape(x,(1,len(x))) 175 | 176 | for i in range(0,N_symbols): 177 | codewords[i*self.n:(i+1)*self.n] = np.matmul(x[:,i*self.k:(i+1)*self.k],self.G)%2 178 | return codewords 179 | 180 | def hamm_decoder(self,codewords): 181 | """ 182 | Decode hamming encoded codewords. Make sure code words are of 183 | the appropriate length for the object. 184 | 185 | parameters 186 | --------- 187 | codewords: bit array of codewords 188 | 189 | returns 190 | ------- 191 | decoded_bits: bit array of decoded source bits 192 | 193 | Andrew Smit November 2018 194 | """ 195 | if(np.dtype(codewords[0]) != int): 196 | raise ValueError('Error: Invalid data type. Input must be a vector of ints') 197 | 198 | if(len(codewords) % self.n or len(codewords) < self.n): 199 | raise ValueError('Error: Invalid input vector length. Length must be a multiple of %d' %self.n) 200 | 201 | # Calculate the number of symbols (codewords) in the input array 202 | N_symbols = int(len(codewords)/self.n) 203 | 204 | # Allocate memory for decoded sourcebits 205 | decoded_bits = np.zeros(N_symbols*self.k) 206 | 207 | # Loop through codewords to decode one block at a time 208 | codewords = np.reshape(codewords,(1,len(codewords))) 209 | for i in range(0,N_symbols): 210 | 211 | # find the syndrome of each codeword 212 | S = np.matmul(self.H,codewords[:,i*self.n:(i+1)*self.n].T) % 2 213 | 214 | # convert binary syndrome to an integer 215 | bits = '' 216 | for m in range(0,len(S)): 217 | bit = str(int(S[m,:])) 218 | bits = bits + bit 219 | error_pos = int(bits,2) 220 | h_pos = self.H[:,error_pos-1] 221 | 222 | # Use the syndrome to find the position of an error within the block 223 | bits = '' 224 | for m in range(0,len(S)): 225 | bit = str(int(h_pos[m])) 226 | bits = bits + bit 227 | decoded_pos = int(bits,2)-1 228 | 229 | # correct error if present 230 | if(error_pos): 231 | codewords[:,i*self.n+decoded_pos] = (codewords[:,i*self.n+decoded_pos] + 1) % 2 232 | 233 | # Decode the corrected codeword 234 | decoded_bits[i*self.k:(i+1)*self.k] = np.matmul(self.R,codewords[:,i*self.n:(i+1)*self.n].T).T % 2 235 | return decoded_bits.astype(int) 236 | 237 | 238 | class FECCyclic(object): 239 | """ 240 | Class responsible for creating cyclic block codes and then 241 | encoding and decoding. Methods provided include 242 | cyclic_encoder(), cyclic_decoder(). 243 | 244 | Parameters 245 | ---------- 246 | G: Generator polynomial used to create cyclic code object 247 | Suggested G values (from Ziemer and Peterson pg 430): 248 | j G 249 | ------------ 250 | 3 G = '1011' 251 | 4 G = '10011' 252 | 5 G = '101001' 253 | 6 G = '1100001' 254 | 7 G = '10100001' 255 | 8 G = '101110001' 256 | 9 G = '1000100001' 257 | 10 G = '10010000001' 258 | 11 G = '101000000001' 259 | 12 G = '1100101000001' 260 | 13 G = '11011000000001' 261 | 14 G = '110000100010001' 262 | 15 G = '1100000000000001' 263 | 16 G = '11010000000010001' 264 | 17 G = '100100000000000001' 265 | 18 G = '1000000100000000001' 266 | 19 G = '11100100000000000001' 267 | 20 G = '100100000000000000001' 268 | 21 G = '1010000000000000000001' 269 | 22 G = '11000000000000000000001' 270 | 23 G = '100001000000000000000001' 271 | 24 G = '1110000100000000000000001' 272 | 273 | Returns 274 | ------- 275 | 276 | Examples 277 | -------- 278 | 279 | Andrew Smit November 2018 280 | """ 281 | 282 | def __init__(self,G='1011'): 283 | self.j = len(G)-1 284 | self.n = 2**self.j - 1 285 | self.k =self.n-self.j 286 | self.G = G 287 | if(G[0] == '0' or G[len(G)-1] == '0'): 288 | raise ValueError('Error: Invalid generator polynomial') 289 | log.info('(%d,%d) cyclic code object' %(self.n,self.k)) 290 | 291 | 292 | def cyclic_encoder(self,x,G='1011'): 293 | """ 294 | Encodes input bit array x using cyclic block code. 295 | 296 | parameters 297 | ---------- 298 | x: vector of source bits to be encoded by block encoder. Numpy array 299 | of integers expected. 300 | 301 | returns 302 | ------- 303 | codewords: vector of code words generated from input vector 304 | 305 | Andrew Smit November 2018 306 | """ 307 | 308 | # Check block length 309 | if(len(x) % self.k or len(x) < self.k): 310 | raise ValueError('Error: Incomplete block in input array. Make sure input array length is a multiple of %d' %self.k) 311 | 312 | # Check data type of input vector 313 | if(np.dtype(x[0]) != int): 314 | raise ValueError('Error: Input array should be int data type') 315 | 316 | # Calculate number of blocks 317 | Num_blocks = int(len(x) / self.k) 318 | 319 | codewords = np.zeros((Num_blocks,self.n),dtype=int) 320 | x = np.reshape(x,(Num_blocks,self.k)) 321 | 322 | #print(x) 323 | 324 | for p in range(Num_blocks): 325 | S = np.zeros(len(self.G)) 326 | codeword = np.zeros(self.n) 327 | current_block = x[p,:] 328 | #print(current_block) 329 | for i in range(0,self.n): 330 | if(i < self.k): 331 | S[0] = current_block[i] 332 | S0temp = 0 333 | for m in range(0,len(self.G)): 334 | if(self.G[m] == '1'): 335 | S0temp = S0temp + S[m] 336 | #print(j,S0temp,S[j]) 337 | S0temp = S0temp % 2 338 | S = np.roll(S,1) 339 | codeword[i] = current_block[i] 340 | S[1] = S0temp 341 | else: 342 | out = 0 343 | for m in range(1,len(self.G)): 344 | if(self.G[m] == '1'): 345 | out = out + S[m] 346 | codeword[i] = out % 2 347 | S = np.roll(S,1) 348 | S[1] = 0 349 | codewords[p,:] = codeword 350 | #print(codeword) 351 | 352 | codewords = np.reshape(codewords,np.size(codewords)) 353 | 354 | return codewords.astype(int) 355 | 356 | 357 | def cyclic_decoder(self,codewords): 358 | """ 359 | Decodes a vector of cyclic coded codewords. 360 | 361 | parameters 362 | ---------- 363 | codewords: vector of codewords to be decoded. Numpy array of integers expected. 364 | 365 | returns 366 | ------- 367 | decoded_blocks: vector of decoded bits 368 | 369 | Andrew Smit November 2018 370 | """ 371 | 372 | # Check block length 373 | if(len(codewords) % self.n or len(codewords) < self.n): 374 | raise ValueError('Error: Incomplete coded block in input array. Make sure coded input array length is a multiple of %d' %self.n) 375 | 376 | # Check input data type 377 | if(np.dtype(codewords[0]) != int): 378 | raise ValueError('Error: Input array should be int data type') 379 | 380 | # Calculate number of blocks 381 | Num_blocks = int(len(codewords) / self.n) 382 | 383 | decoded_blocks = np.zeros((Num_blocks,self.k),dtype=int) 384 | codewords = np.reshape(codewords,(Num_blocks,self.n)) 385 | 386 | for p in range(Num_blocks): 387 | codeword = codewords[p,:] 388 | Ureg = np.zeros(self.n) 389 | S = np.zeros(len(self.G)) 390 | decoded_bits = np.zeros(self.k) 391 | output = np.zeros(self.n) 392 | for i in range(0,self.n): # Switch A closed B open 393 | Ureg = np.roll(Ureg,1) 394 | Ureg[0] = codeword[i] 395 | S0temp = 0 396 | S[0] = codeword[i] 397 | for m in range(len(self.G)): 398 | if(self.G[m] == '1'): 399 | S0temp = S0temp + S[m] 400 | S0 = S 401 | S = np.roll(S,1) 402 | S[1] = S0temp % 2 403 | 404 | for i in range(0,self.n): # Switch B closed A open 405 | Stemp = 0 406 | for m in range(1,len(self.G)): 407 | if(self.G[m] == '1'): 408 | Stemp = Stemp + S[m] 409 | S = np.roll(S,1) 410 | S[1] = Stemp % 2 411 | and_out = 1 412 | for m in range(1,len(self.G)): 413 | if(m > 1): 414 | and_out = and_out and ((S[m]+1) % 2) 415 | else: 416 | and_out = and_out and S[m] 417 | output[i] = (and_out + Ureg[len(Ureg)-1]) % 2 418 | Ureg = np.roll(Ureg,1) 419 | Ureg[0] = 0 420 | decoded_bits = output[0:self.k].astype(int) 421 | decoded_blocks[p,:] = decoded_bits 422 | 423 | return np.reshape(decoded_blocks,np.size(decoded_blocks)).astype(int) 424 | 425 | def ser2ber(q,n,d,t,ps): 426 | """ 427 | Converts symbol error rate to bit error rate. Taken from Ziemer and 428 | Tranter page 650. Necessary when comparing different types of block codes. 429 | 430 | parameters 431 | ---------- 432 | q: size of the code alphabet for given modulation type (BPSK=2) 433 | n: number of channel bits 434 | d: distance (2e+1) where e is the number of correctable errors per code word. 435 | For hamming codes, e=1, so d=3. 436 | t: number of correctable errors per code word 437 | ps: symbol error probability vector 438 | 439 | returns 440 | ------- 441 | ber: bit error rate 442 | 443 | """ 444 | lnps = len(ps) # len of error vector 445 | ber = np.zeros(lnps) # inialize output vector 446 | for k in range(0,lnps): # iterate error vector 447 | ser = ps[k] # channel symbol error rate 448 | sum1 = 0 # initialize sums 449 | sum2 = 0 450 | for i in range(t+1,d+1): 451 | term = special.comb(n,i)*(ser**i)*((1-ser))**(n-i) 452 | sum1 = sum1 + term 453 | for i in range(d+1,n+1): 454 | term = (i)*special.comb(n,i)*(ser**i)*((1-ser)**(n-i)) 455 | sum2 = sum2+term 456 | ber[k] = (q/(2*(q-1)))*((d/n)*sum1+(1/n)*sum2) 457 | 458 | return ber 459 | 460 | def block_single_error_Pb_bound(j,SNRdB,coded=True,M=2): 461 | """ 462 | Finds the bit error probability bounds according to Ziemer and Tranter 463 | page 656. 464 | 465 | parameters: 466 | ----------- 467 | j: number of parity bits used in single error correction block code 468 | SNRdB: Eb/N0 values in dB 469 | coded: Select single error correction code (True) or uncoded (False) 470 | M: modulation order 471 | 472 | returns: 473 | -------- 474 | Pb: bit error probability bound 475 | 476 | """ 477 | Pb = np.zeros_like(SNRdB) 478 | Ps = np.zeros_like(SNRdB) 479 | SNR = 10.**(SNRdB/10.) 480 | n = 2**j-1 481 | k = n-j 482 | 483 | for i,SNRn in enumerate(SNR): 484 | if coded: # compute Hamming code Ps 485 | if M == 2: 486 | Ps[i] = q_fctn(np.sqrt(k * 2. * SNRn / n)) 487 | else: 488 | Ps[i] = 4./np.log2(M)*(1 - 1/np.sqrt(M))*\ 489 | np.gaussQ(np.sqrt(3*np.log2(M)/(M-1)*SNRn))/k 490 | else: # Compute Uncoded Pb 491 | if M == 2: 492 | Pb[i] = q_fctn(np.sqrt(2. * SNRn)) 493 | else: 494 | Pb[i] = 4./np.log2(M)*(1 - 1/np.sqrt(M))*\ 495 | np.gaussQ(np.sqrt(3*np.log2(M)/(M-1)*SNRn)) 496 | 497 | # Convert symbol error probability to bit error probability 498 | if coded: 499 | Pb = ser2ber(M,n,3,1,Ps) 500 | return Pb 501 | 502 | # .. ._.. .._ # -------------------------------------------------------------------------------- /src/sk_dsp_comm/fir_design_helper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic Linear Phase Digital Filter Design Helper 3 | 4 | Copyright (c) March 2017, Mark Wickert 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | The views and conclusions contained in the software and documentation are those 28 | of the authors and should not be interpreted as representing official policies, 29 | either expressed or implied, of the FreeBSD Project. 30 | """ 31 | 32 | import numpy as np 33 | import scipy.signal as signal 34 | from scipy.signal.windows import kaiser 35 | import matplotlib.pyplot as plt 36 | from logging import getLogger 37 | log = getLogger(__name__) 38 | 39 | 40 | def firwin_lpf(n_taps, fc, fs = 1.0): 41 | """ 42 | Design a windowed FIR lowpass filter in terms of passband 43 | critical frequencies f1 < f2 in Hz relative to sampling rate 44 | fs in Hz. The number of taps must be provided. 45 | 46 | Mark Wickert October 2016 47 | """ 48 | return signal.firwin(n_taps, 2 * fc / fs) 49 | 50 | 51 | def firwin_bpf(n_taps, f1, f2, fs = 1.0, pass_zero=False): 52 | """ 53 | Design a windowed FIR bandpass filter in terms of passband 54 | critical frequencies f1 < f2 in Hz relative to sampling rate 55 | fs in Hz. The number of taps must be provided. 56 | 57 | Mark Wickert October 2016 58 | """ 59 | return signal.firwin(n_taps, 2 * (f1, f2) / fs, pass_zero=pass_zero) 60 | 61 | 62 | def firwin_kaiser_lpf(f_pass, f_stop, d_stop, fs = 1.0, n_bump=0, status = True): 63 | """ 64 | Design an FIR lowpass filter using the sinc() kernel and 65 | a Kaiser window. The filter order is determined based on 66 | f_pass Hz, f_stop Hz, and the desired stopband attenuation 67 | d_stop in dB, all relative to a sampling rate of fs Hz. 68 | Note: the passband ripple cannot be set independent of the 69 | stopband attenuation. 70 | 71 | Mark Wickert October 2016 72 | """ 73 | wc = 2*np.pi*(f_pass + f_stop)/2/fs 74 | delta_w = 2*np.pi*(f_stop - f_pass)/fs 75 | # Find the filter order 76 | M = np.ceil((d_stop - 8)/(2.285*delta_w)) 77 | # Adjust filter order up or down as needed 78 | M += n_bump 79 | N_taps = M + 1 80 | # Obtain the Kaiser window 81 | beta = signal.kaiser_beta(d_stop) 82 | w_k = kaiser(N_taps,beta) 83 | n = np.arange(N_taps) 84 | b_k = wc/np.pi*np.sinc(wc/np.pi*(n-M/2)) * w_k 85 | b_k /= np.sum(b_k) 86 | if status: 87 | log.info('Kaiser Win filter taps = %d.' % N_taps) 88 | return b_k 89 | 90 | 91 | def firwin_kaiser_hpf(f_stop, f_pass, d_stop, fs = 1.0, n_bump=0, status = True): 92 | """ 93 | Design an FIR highpass filter using the sinc() kernel and 94 | a Kaiser window. The filter order is determined based on 95 | f_pass Hz, f_stop Hz, and the desired stopband attenuation 96 | d_stop in dB, all relative to a sampling rate of fs Hz. 97 | Note: the passband ripple cannot be set independent of the 98 | stopband attenuation. 99 | 100 | Mark Wickert October 2016 101 | """ 102 | # Transform HPF critical frequencies to lowpass equivalent 103 | f_pass_eq = fs/2. - f_pass 104 | f_stop_eq = fs/2. - f_stop 105 | # Design LPF equivalent 106 | wc = 2*np.pi*(f_pass_eq + f_stop_eq)/2/fs 107 | delta_w = 2*np.pi*(f_stop_eq - f_pass_eq)/fs 108 | # Find the filter order 109 | M = np.ceil((d_stop - 8)/(2.285*delta_w)) 110 | # Adjust filter order up or down as needed 111 | M += n_bump 112 | N_taps = M + 1 113 | # Obtain the Kaiser window 114 | beta = signal.kaiser_beta(d_stop) 115 | w_k = kaiser(N_taps,beta) 116 | n = np.arange(N_taps) 117 | b_k = wc/np.pi*np.sinc(wc/np.pi*(n-M/2)) * w_k 118 | b_k /= np.sum(b_k) 119 | # Transform LPF equivalent to HPF 120 | n = np.arange(len(b_k)) 121 | b_k *= (-1)**n 122 | if status: 123 | log.info('Kaiser Win filter taps = %d.' % N_taps) 124 | return b_k 125 | 126 | 127 | def firwin_kaiser_bpf(f_stop1, f_pass1, f_pass2, f_stop2, d_stop, 128 | fs = 1.0, n_bump=0, status = True): 129 | """ 130 | Design an FIR bandpass filter using the sinc() kernel and 131 | a Kaiser window. The filter order is determined based on 132 | f_stop1 Hz, f_pass1 Hz, f_pass2 Hz, f_stop2 Hz, and the 133 | desired stopband attenuation d_stop in dB for both stopbands, 134 | all relative to a sampling rate of fs Hz. 135 | Note: the passband ripple cannot be set independent of the 136 | stopband attenuation. 137 | 138 | Mark Wickert October 2016 139 | """ 140 | # Design BPF starting from simple LPF equivalent 141 | # The upper and lower stopbands are assumed to have 142 | # the same attenuation level. The LPF equivalent critical 143 | # frequencies: 144 | f_pass = (f_pass2 - f_pass1)/2 145 | f_stop = (f_stop2 - f_stop1)/2 146 | # Continue to design equivalent LPF 147 | wc = 2*np.pi*(f_pass + f_stop)/2/fs 148 | delta_w = 2*np.pi*(f_stop - f_pass)/fs 149 | # Find the filter order 150 | M = np.ceil((d_stop - 8)/(2.285*delta_w)) 151 | # Adjust filter order up or down as needed 152 | M += n_bump 153 | N_taps = M + 1 154 | # Obtain the Kaiser window 155 | beta = signal.kaiser_beta(d_stop) 156 | w_k = kaiser(N_taps,beta) 157 | n = np.arange(N_taps) 158 | b_k = wc/np.pi*np.sinc(wc/np.pi*(n-M/2)) * w_k 159 | b_k /= np.sum(b_k) 160 | # Transform LPF to BPF 161 | f0 = (f_pass2 + f_pass1)/2 162 | w0 = 2*np.pi*f0/fs 163 | n = np.arange(len(b_k)) 164 | b_k_bp = 2*b_k*np.cos(w0*(n-M/2)) 165 | if status: 166 | log.info('Kaiser Win filter taps = %d.' % N_taps) 167 | return b_k_bp 168 | 169 | 170 | def firwin_kaiser_bsf(f_stop1, f_pass1, f_pass2, f_stop2, d_stop, 171 | fs = 1.0, n_bump=0, status = True): 172 | """ 173 | Design an FIR bandstop filter using the sinc() kernel and 174 | a Kaiser window. The filter order is determined based on 175 | f_stop1 Hz, f_pass1 Hz, f_pass2 Hz, f_stop2 Hz, and the 176 | desired stopband attenuation d_stop in dB for both stopbands, 177 | all relative to a sampling rate of fs Hz. 178 | Note: The passband ripple cannot be set independent of the 179 | stopband attenuation. 180 | Note: The filter order is forced to be even (odd number of taps) 181 | so there is a center tap that can be used to form 1 - H_BPF. 182 | 183 | Mark Wickert October 2016 184 | """ 185 | # First design a BPF starting from simple LPF equivalent 186 | # The upper and lower stopbands are assumed to have 187 | # the same attenuation level. The LPF equivalent critical 188 | # frequencies: 189 | f_pass = (f_pass2 - f_pass1)/2 190 | f_stop = (f_stop2 - f_stop1)/2 191 | # Continue to design equivalent LPF 192 | wc = 2*np.pi*(f_pass + f_stop)/2/fs 193 | delta_w = 2*np.pi*(f_stop - f_pass)/fs 194 | # Find the filter order 195 | M = np.ceil((d_stop - 8)/(2.285*delta_w)) 196 | # Adjust filter order up or down as needed 197 | M += n_bump 198 | # Make filter order even (odd number of taps) 199 | if ((M+1)/2.0-int((M+1)/2.0)) == 0: 200 | M += 1 201 | N_taps = M + 1 202 | # Obtain the Kaiser window 203 | beta = signal.kaiser_beta(d_stop) 204 | w_k = kaiser(N_taps,beta) 205 | n = np.arange(N_taps) 206 | b_k = wc/np.pi*np.sinc(wc/np.pi*(n-M/2)) * w_k 207 | b_k /= np.sum(b_k) 208 | # Transform LPF to BPF 209 | f0 = (f_pass2 + f_pass1)/2 210 | w0 = 2*np.pi*f0/fs 211 | n = np.arange(len(b_k)) 212 | b_k_bs = 2*b_k*np.cos(w0*(n-M/2)) 213 | # Transform BPF to BSF via 1 - BPF for odd N_taps 214 | b_k_bs = -b_k_bs 215 | b_k_bs[int(M/2)] += 1 216 | if status: 217 | log.info('Kaiser Win filter taps = %d.' % N_taps) 218 | return b_k_bs 219 | 220 | 221 | def lowpass_order(f_pass, f_stop, dpass_dB, dstop_dB, fsamp = 1): 222 | """ 223 | Optimal FIR (equal ripple) Lowpass Order Determination 224 | 225 | Text reference: Ifeachor, Digital Signal Processing a Practical Approach, 226 | second edition, Prentice Hall, 2002. 227 | Journal paper reference: Herriman et al., Practical Design Rules for Optimum 228 | Finite Imulse Response Digitl Filters, Bell Syst. Tech. J., vol 52, pp. 229 | 769-799, July-Aug., 1973.IEEE, 1973. 230 | """ 231 | dpass = 1 - 10**(-dpass_dB/20) 232 | dstop = 10**(-dstop_dB/20) 233 | Df = (f_stop - f_pass)/fsamp 234 | a1 = 5.309e-3 235 | a2 = 7.114e-2 236 | a3 = -4.761e-1 237 | a4 = -2.66e-3 238 | a5 = -5.941e-1 239 | a6 = -4.278e-1 240 | 241 | Dinf = np.log10(dstop)*(a1*np.log10(dpass)**2 + a2*np.log10(dpass) + a3) \ 242 | + (a4*np.log10(dpass)**2 + a5*np.log10(dpass) + a6) 243 | f = 11.01217 + 0.51244*(np.log10(dpass) - np.log10(dstop)) 244 | N = Dinf/Df - f*Df + 1 245 | ff = 2*np.array([0, f_pass, f_stop, fsamp/2])/fsamp 246 | aa = np.array([1, 1, 0, 0]) 247 | wts = np.array([1.0, dpass/dstop]) 248 | return int(N), ff, aa, wts 249 | 250 | 251 | def bandpass_order(f_stop1, f_pass1, f_pass2, f_stop2, dpass_dB, dstop_dB, fsamp = 1): 252 | """ 253 | Optimal FIR (equal ripple) Bandpass Order Determination 254 | 255 | Text reference: Ifeachor, Digital Signal Processing a Practical Approach, 256 | second edition, Prentice Hall, 2002. 257 | Journal paper reference: F. Mintzer & B. Liu, Practical Design Rules for Optimum 258 | FIR Bandpass Digital Filters, IEEE Transactions on Acoustics and Speech, pp. 259 | 204-206, April,1979. 260 | """ 261 | dpass = 1 - 10**(-dpass_dB/20) 262 | dstop = 10**(-dstop_dB/20) 263 | Df1 = (f_pass1 - f_stop1)/fsamp 264 | Df2 = (f_stop2 - f_pass2)/fsamp 265 | b1 = 0.01201 266 | b2 = 0.09664 267 | b3 = -0.51325 268 | b4 = 0.00203 269 | b5 = -0.5705 270 | b6 = -0.44314 271 | 272 | Df = min(Df1, Df2) 273 | Cinf = np.log10(dstop)*(b1*np.log10(dpass)**2 + b2*np.log10(dpass) + b3) \ 274 | + (b4*np.log10(dpass)**2 + b5*np.log10(dpass) + b6) 275 | g = -14.6*np.log10(dpass/dstop) - 16.9 276 | N = Cinf/Df + g*Df + 1 277 | ff = 2*np.array([0, f_stop1, f_pass1, f_pass2, f_stop2, fsamp/2])/fsamp 278 | aa = np.array([0, 0, 1, 1, 0, 0]) 279 | wts = np.array([dpass/dstop, 1, dpass/dstop]) 280 | return int(N), ff, aa, wts 281 | 282 | 283 | def bandstop_order(f_stop1, f_pass1, f_pass2, f_stop2, dpass_dB, dstop_dB, fsamp = 1): 284 | """ 285 | Optimal FIR (equal ripple) Bandstop Order Determination 286 | 287 | Text reference: Ifeachor, Digital Signal Processing a Practical Approach, 288 | second edition, Prentice Hall, 2002. 289 | Journal paper reference: F. Mintzer & B. Liu, Practical Design Rules for Optimum 290 | FIR Bandpass Digital Filters, IEEE Transactions on Acoustics and Speech, pp. 291 | 204-206, April,1979. 292 | """ 293 | dpass = 1 - 10**(-dpass_dB/20) 294 | dstop = 10**(-dstop_dB/20) 295 | Df1 = (f_pass1 - f_stop1)/fsamp 296 | Df2 = (f_stop2 - f_pass2)/fsamp 297 | b1 = 0.01201 298 | b2 = 0.09664 299 | b3 = -0.51325 300 | b4 = 0.00203 301 | b5 = -0.5705 302 | b6 = -0.44314 303 | 304 | Df = min(Df1, Df2) 305 | Cinf = np.log10(dstop)*(b1*np.log10(dpass)**2 + b2*np.log10(dpass) + b3) \ 306 | + (b4*np.log10(dpass)**2 + b5*np.log10(dpass) + b6) 307 | g = -14.6*np.log10(dpass/dstop) - 16.9 308 | N = Cinf/Df + g*Df + 1 309 | ff = 2*np.array([0, f_stop1, f_pass1, f_pass2, f_stop2, fsamp/2])/fsamp 310 | aa = np.array([1, 1, 0, 0, 1, 1]) 311 | wts = np.array([2, dpass/dstop, 2]) 312 | return int(N), ff, aa, wts 313 | 314 | 315 | def fir_remez_lpf(f_pass, f_stop, d_pass, d_stop, fs = 1.0, n_bump=5, status = True): 316 | """ 317 | Design an FIR lowpass filter using remez with order 318 | determination. The filter order is determined based on 319 | f_pass Hz, fstop Hz, and the desired passband ripple 320 | d_pass dB and stopband attenuation d_stop dB all 321 | relative to a sampling rate of fs Hz. 322 | 323 | Mark Wickert October 2016, updated October 2018 324 | """ 325 | n, ff, aa, wts = lowpass_order(f_pass, f_stop, d_pass, d_stop, fsamp=fs) 326 | # Bump up the order by N_bump to bring down the final d_pass & d_stop 327 | N_taps = n 328 | N_taps += n_bump 329 | b = signal.remez(N_taps, ff, aa[0::2], weight=wts, fs=2) 330 | if status: 331 | log.info('Remez filter taps = %d.' % N_taps) 332 | return b 333 | 334 | 335 | def fir_remez_hpf(f_stop, f_pass, d_pass, d_stop, fs = 1.0, n_bump=5, status = True): 336 | """ 337 | Design an FIR highpass filter using remez with order 338 | determination. The filter order is determined based on 339 | f_pass Hz, fstop Hz, and the desired passband ripple 340 | d_pass dB and stopband attenuation d_stop dB all 341 | relative to a sampling rate of fs Hz. 342 | 343 | Mark Wickert October 2016, updated October 2018 344 | """ 345 | # Transform HPF critical frequencies to lowpass equivalent 346 | f_pass_eq = fs/2. - f_pass 347 | f_stop_eq = fs/2. - f_stop 348 | # Design LPF equivalent 349 | n, ff, aa, wts = lowpass_order(f_pass_eq, f_stop_eq, d_pass, d_stop, fsamp=fs) 350 | # Bump up the order by N_bump to bring down the final d_pass & d_stop 351 | N_taps = n 352 | N_taps += n_bump 353 | b = signal.remez(N_taps, ff, aa[0::2], weight=wts,fs=2) 354 | # Transform LPF equivalent to HPF 355 | n = np.arange(len(b)) 356 | b *= (-1)**n 357 | if status: 358 | log.info('Remez filter taps = %d.' % N_taps) 359 | return b 360 | 361 | 362 | def fir_remez_bpf(f_stop1, f_pass1, f_pass2, f_stop2, d_pass, d_stop, 363 | fs = 1.0, n_bump=5, status = True): 364 | """ 365 | Design an FIR bandpass filter using remez with order 366 | determination. The filter order is determined based on 367 | f_stop1 Hz, f_pass1 Hz, f_pass2 Hz, f_stop2 Hz, and the 368 | desired passband ripple d_pass dB and stopband attenuation 369 | d_stop dB all relative to a sampling rate of fs Hz. 370 | 371 | Mark Wickert October 2016, updated October 2018 372 | """ 373 | n, ff, aa, wts = bandpass_order(f_stop1, f_pass1, f_pass2, f_stop2, 374 | d_pass, d_stop, fsamp=fs) 375 | # Bump up the order by N_bump to bring down the final d_pass & d_stop 376 | N_taps = n 377 | N_taps += n_bump 378 | b = signal.remez(N_taps, ff, aa[0::2], weight=wts,fs=2) 379 | if status: 380 | log.info('Remez filter taps = %d.' % N_taps) 381 | return b 382 | 383 | 384 | def fir_remez_bsf(f_pass1, f_stop1, f_stop2, f_pass2, d_pass, d_stop, 385 | fs = 1.0, n_bump=5, status = True): 386 | """ 387 | Design an FIR bandstop filter using remez with order 388 | determination. The filter order is determined based on 389 | f_pass1 Hz, f_stop1 Hz, f_stop2 Hz, f_pass2 Hz, and the 390 | desired passband ripple d_pass dB and stopband attenuation 391 | d_stop dB all relative to a sampling rate of fs Hz. 392 | 393 | Mark Wickert October 2016, updated October 2018 394 | """ 395 | n, ff, aa, wts = bandstop_order(f_pass1, f_stop1, f_stop2, f_pass2, 396 | d_pass, d_stop, fsamp=fs) 397 | # Bump up the order by N_bump to bring down the final d_pass & d_stop 398 | # Initially make sure the number of taps is even so N_bump needs to be odd 399 | if np.mod(n,2) != 0: 400 | n += 1 401 | N_taps = n 402 | N_taps += n_bump 403 | b = signal.remez(N_taps, ff, aa[0::2], weight=wts, fs=2) 404 | if status: 405 | log.info('N_bump must be odd to maintain odd filter length') 406 | log.info('Remez filter taps = %d.' % N_taps) 407 | return b 408 | 409 | 410 | def freqz_resp_list(b, a=np.array([1]), mode = 'dB', fs=1.0, n_pts = 1024, fsize=(6, 4)): 411 | """ 412 | A method for displaying digital filter frequency response magnitude, 413 | phase, and group delay. A plot is produced using matplotlib 414 | 415 | freq_resp(self,mode = 'dB',Npts = 1024) 416 | 417 | A method for displaying the filter frequency response magnitude, 418 | phase, and group delay. A plot is produced using matplotlib 419 | 420 | freqz_resp(b,a=[1],mode = 'dB',Npts = 1024,fsize=(6,4)) 421 | 422 | b = ndarray of numerator coefficients 423 | a = ndarray of denominator coefficents 424 | mode = display mode: 'dB' magnitude, 'phase' in radians, or 425 | 'groupdelay_s' in samples and 'groupdelay_t' in sec, 426 | all versus frequency in Hz 427 | Npts = number of points to plot; default is 1024 428 | fsize = figure size; defult is (6,4) inches 429 | 430 | Mark Wickert, January 2015 431 | """ 432 | if type(b) == list: 433 | # We have a list of filters 434 | N_filt = len(b) 435 | f = np.arange(0, n_pts) / (2.0 * n_pts) 436 | for n in range(N_filt): 437 | w,H = signal.freqz(b[n],a[n],2*np.pi*f) 438 | if n == 0: 439 | plt.figure(figsize=fsize) 440 | if mode.lower() == 'db': 441 | plt.plot(f*fs,20*np.log10(np.abs(H))) 442 | if n == N_filt-1: 443 | plt.xlabel('Frequency (Hz)') 444 | plt.ylabel('Gain (dB)') 445 | plt.title('Frequency Response - Magnitude') 446 | 447 | elif mode.lower() == 'phase': 448 | plt.plot(f*fs,np.angle(H)) 449 | if n == N_filt-1: 450 | plt.xlabel('Frequency (Hz)') 451 | plt.ylabel('Phase (rad)') 452 | plt.title('Frequency Response - Phase') 453 | 454 | elif (mode.lower() == 'groupdelay_s') or (mode.lower() == 'groupdelay_t'): 455 | """ 456 | Notes 457 | ----- 458 | 459 | Since this calculation involves finding the derivative of the 460 | phase response, care must be taken at phase wrapping points 461 | and when the phase jumps by +/-pi, which occurs when the 462 | amplitude response changes sign. Since the amplitude response 463 | is zero when the sign changes, the jumps do not alter the group 464 | delay results. 465 | """ 466 | theta = np.unwrap(np.angle(H)) 467 | # Since theta for an FIR filter is likely to have many pi phase 468 | # jumps too, we unwrap a second time 2*theta and divide by 2 469 | theta2 = np.unwrap(2*theta)/2. 470 | theta_dif = np.diff(theta2) 471 | f_diff = np.diff(f) 472 | Tg = -np.diff(theta2)/np.diff(w) 473 | # For gain almost zero set groupdelay = 0 474 | idx = np.nonzero(np.ravel(20*np.log10(H[:-1]) < -400))[0] 475 | Tg[idx] = np.zeros(len(idx)) 476 | max_Tg = np.max(Tg) 477 | #print(max_Tg) 478 | if mode.lower() == 'groupdelay_t': 479 | max_Tg /= fs 480 | plt.plot(f[:-1]*fs,Tg/fs) 481 | plt.ylim([0,1.2*max_Tg]) 482 | else: 483 | plt.plot(f[:-1]*fs,Tg) 484 | plt.ylim([0,1.2*max_Tg]) 485 | if n == N_filt-1: 486 | plt.xlabel('Frequency (Hz)') 487 | if mode.lower() == 'groupdelay_t': 488 | plt.ylabel('Group Delay (s)') 489 | else: 490 | plt.ylabel('Group Delay (samples)') 491 | plt.title('Frequency Response - Group Delay') 492 | else: 493 | s1 = 'Error, mode must be "dB", "phase, ' 494 | s2 = '"groupdelay_s", or "groupdelay_t"' 495 | log.info(s1 + s2) 496 | -------------------------------------------------------------------------------- /src/sk_dsp_comm/iir_design_helper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic IIR Bilinear Transform-Based Digital Filter Design Helper 3 | 4 | Copyright (c) March 2017, Mark Wickert 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | The views and conclusions contained in the software and documentation are those 28 | of the authors and should not be interpreted as representing official policies, 29 | either expressed or implied, of the FreeBSD Project. 30 | """ 31 | 32 | import numpy as np 33 | import scipy.signal as signal 34 | import matplotlib.pyplot as plt 35 | from logging import getLogger 36 | log = getLogger(__name__) 37 | 38 | 39 | def IIR_lpf(f_pass, f_stop, Ripple_pass, Atten_stop, 40 | fs = 1.00, ftype = 'butter', status = True): 41 | """ 42 | Design an IIR lowpass filter using scipy.signal.iirdesign. 43 | The filter order is determined based on 44 | f_pass Hz, f_stop Hz, and the desired stopband attenuation 45 | d_stop in dB, all relative to a sampling rate of fs Hz. 46 | 47 | Parameters 48 | ---------- 49 | f_pass : Passband critical frequency in Hz 50 | f_stop : Stopband critical frequency in Hz 51 | Ripple_pass : Filter gain in dB at f_pass 52 | Atten_stop : Filter attenuation in dB at f_stop 53 | fs : Sampling rate in Hz 54 | ftype : Analog prototype from 'butter' 'cheby1', 'cheby2', 55 | 'ellip', and 'bessel' 56 | 57 | Returns 58 | ------- 59 | b : ndarray of the numerator coefficients 60 | a : ndarray of the denominator coefficients 61 | sos : 2D ndarray of second-order section coefficients 62 | 63 | Notes 64 | ----- 65 | Additionally a text string telling the user the filter order is 66 | written to the console, e.g., IIR cheby1 order = 8. 67 | 68 | Examples 69 | -------- 70 | >>> fs = 48000 71 | >>> f_pass = 5000 72 | >>> f_stop = 8000 73 | >>> b_but,a_but,sos_but = IIR_lpf(f_pass,f_stop,0.5,60,fs,'butter') 74 | >>> b_cheb1,a_cheb1,sos_cheb1 = IIR_lpf(f_pass,f_stop,0.5,60,fs,'cheby1') 75 | >>> b_cheb2,a_cheb2,sos_cheb2 = IIR_lpf(f_pass,f_stop,0.5,60,fs,'cheby2') 76 | >>> b_elli,a_elli,sos_elli = IIR_lpf(f_pass,f_stop,0.5,60,fs,'ellip') 77 | 78 | 79 | Mark Wickert October 2016 80 | """ 81 | 82 | b,a = signal.iirdesign(2*float(f_pass)/fs, 2*float(f_stop)/fs, 83 | Ripple_pass, Atten_stop, 84 | ftype = ftype, output='ba') 85 | sos = signal.iirdesign(2*float(f_pass)/fs, 2*float(f_stop)/fs, 86 | Ripple_pass, Atten_stop, 87 | ftype = ftype, output='sos') 88 | tag = 'IIR ' + ftype + ' order' 89 | if status: 90 | log.info('%s = %d.' % (tag,len(a)-1)) 91 | return b, a, sos 92 | 93 | 94 | def IIR_hpf(f_stop, f_pass, Ripple_pass, Atten_stop, 95 | fs = 1.00, ftype = 'butter', status = True): 96 | """ 97 | Design an IIR highpass filter using scipy.signal.iirdesign. 98 | The filter order is determined based on 99 | f_pass Hz, f_stop Hz, and the desired stopband attenuation 100 | d_stop in dB, all relative to a sampling rate of fs Hz. 101 | 102 | Parameters 103 | ---------- 104 | f_stop : 105 | f_pass : 106 | Ripple_pass : 107 | Atten_stop : 108 | fs : sampling rate in Hz 109 | ftype : Analog prototype from 'butter' 'cheby1', 'cheby2', 110 | 'ellip', and 'bessel' 111 | 112 | Returns 113 | ------- 114 | b : ndarray of the numerator coefficients 115 | a : ndarray of the denominator coefficients 116 | sos : 2D ndarray of second-order section coefficients 117 | 118 | Examples 119 | -------- 120 | >>> fs = 48000 121 | >>> f_pass = 8000 122 | >>> f_stop = 5000 123 | >>> b_but,a_but,sos_but = IIR_hpf(f_stop,f_pass,0.5,60,fs,'butter') 124 | >>> b_cheb1,a_cheb1,sos_cheb1 = IIR_hpf(f_stop,f_pass,0.5,60,fs,'cheby1') 125 | >>> b_cheb2,a_cheb2,sos_cheb2 = IIR_hpf(f_stop,f_pass,0.5,60,fs,'cheby2') 126 | >>> b_elli,a_elli,sos_elli = IIR_hpf(f_stop,f_pass,0.5,60,fs,'ellip') 127 | 128 | Mark Wickert October 2016 129 | """ 130 | 131 | b,a = signal.iirdesign(2*float(f_pass)/fs, 2*float(f_stop)/fs, 132 | Ripple_pass, Atten_stop, 133 | ftype = ftype, output='ba') 134 | sos = signal.iirdesign(2*float(f_pass)/fs, 2*float(f_stop)/fs, 135 | Ripple_pass, Atten_stop, 136 | ftype =ftype, output='sos') 137 | tag = 'IIR ' + ftype + ' order' 138 | if status: 139 | log.info('%s = %d.' % (tag,len(a)-1)) 140 | return b, a, sos 141 | 142 | 143 | def IIR_bpf(f_stop1, f_pass1, f_pass2, f_stop2, Ripple_pass, Atten_stop, 144 | fs = 1.00, ftype = 'butter', status = True): 145 | """ 146 | Design an IIR bandpass filter using scipy.signal.iirdesign. 147 | The filter order is determined based on 148 | f_pass Hz, f_stop Hz, and the desired stopband attenuation 149 | d_stop in dB, all relative to a sampling rate of fs Hz. 150 | 151 | Parameters 152 | ---------- 153 | f_stop1 : ndarray of the numerator coefficients 154 | f_pass : ndarray of the denominator coefficients 155 | Ripple_pass : 156 | Atten_stop : 157 | fs : sampling rate in Hz 158 | ftype : Analog prototype from 'butter' 'cheby1', 'cheby2', 159 | 'ellip', and 'bessel' 160 | 161 | Returns 162 | ------- 163 | b : ndarray of the numerator coefficients 164 | a : ndarray of the denominator coefficients 165 | sos : 2D ndarray of second-order section coefficients 166 | 167 | Examples 168 | -------- 169 | >>> fs = 48000 170 | >>> f_pass = 8000 171 | >>> f_stop = 5000 172 | >>> b_but,a_but,sos_but = IIR_hpf(f_stop,f_pass,0.5,60,fs,'butter') 173 | >>> b_cheb1,a_cheb1,sos_cheb1 = IIR_hpf(f_stop,f_pass,0.5,60,fs,'cheby1') 174 | >>> b_cheb2,a_cheb2,sos_cheb2 = IIR_hpf(f_stop,f_pass,0.5,60,fs,'cheby2') 175 | >>> b_elli,a_elli,sos_elli = IIR_hpf(f_stop,f_pass,0.5,60,fs,'ellip') 176 | 177 | Mark Wickert October 2016 178 | """ 179 | 180 | b,a = signal.iirdesign([2*float(f_pass1)/fs, 2*float(f_pass2)/fs], 181 | [2*float(f_stop1)/fs, 2*float(f_stop2)/fs], 182 | Ripple_pass, Atten_stop, 183 | ftype = ftype, output='ba') 184 | sos = signal.iirdesign([2*float(f_pass1)/fs, 2*float(f_pass2)/fs], 185 | [2*float(f_stop1)/fs, 2*float(f_stop2)/fs], 186 | Ripple_pass, Atten_stop, 187 | ftype =ftype, output='sos') 188 | tag = 'IIR ' + ftype + ' order' 189 | if status: 190 | log.info('%s = %d.' % (tag,len(a)-1)) 191 | return b, a, sos 192 | 193 | def IIR_bsf(f_pass1, f_stop1, f_stop2, f_pass2, Ripple_pass, Atten_stop, 194 | fs = 1.00, ftype = 'butter', status = True): 195 | """ 196 | Design an IIR bandstop filter using scipy.signal.iirdesign. 197 | The filter order is determined based on 198 | f_pass Hz, f_stop Hz, and the desired stopband attenuation 199 | d_stop in dB, all relative to a sampling rate of fs Hz. 200 | 201 | Mark Wickert October 2016 202 | """ 203 | 204 | b,a = signal.iirdesign([2*float(f_pass1)/fs, 2*float(f_pass2)/fs], 205 | [2*float(f_stop1)/fs, 2*float(f_stop2)/fs], 206 | Ripple_pass, Atten_stop, 207 | ftype = ftype, output='ba') 208 | sos = signal.iirdesign([2*float(f_pass1)/fs, 2*float(f_pass2)/fs], 209 | [2*float(f_stop1)/fs, 2*float(f_stop2)/fs], 210 | Ripple_pass, Atten_stop, 211 | ftype =ftype, output='sos') 212 | tag = 'IIR ' + ftype + ' order' 213 | if status: 214 | log.info('%s = %d.' % (tag,len(a)-1)) 215 | return b, a, sos 216 | 217 | def freqz_resp_list(b,a=np.array([1]),mode = 'dB',fs=1.0,Npts = 1024,fsize=(6,4)): 218 | """ 219 | A method for displaying digital filter frequency response magnitude, 220 | phase, and group delay. A plot is produced using matplotlib 221 | 222 | freq_resp(self,mode = 'dB',Npts = 1024) 223 | 224 | A method for displaying the filter frequency response magnitude, 225 | phase, and group delay. A plot is produced using matplotlib 226 | 227 | freqz_resp(b,a=[1],mode = 'dB',Npts = 1024,fsize=(6,4)) 228 | 229 | b = ndarray of numerator coefficients 230 | a = ndarray of denominator coefficents 231 | mode = display mode: 'dB' magnitude, 'phase' in radians, or 232 | 'groupdelay_s' in samples and 'groupdelay_t' in sec, 233 | all versus frequency in Hz 234 | Npts = number of points to plot; default is 1024 235 | fsize = figure size; defult is (6,4) inches 236 | 237 | Mark Wickert, January 2015 238 | """ 239 | if type(b) == list: 240 | # We have a list of filters 241 | N_filt = len(b) 242 | f = np.arange(0,Npts)/(2.0*Npts) 243 | for n in range(N_filt): 244 | w,H = signal.freqz(b[n],a[n],2*np.pi*f) 245 | if n == 0: 246 | plt.figure(figsize=fsize) 247 | if mode.lower() == 'db': 248 | plt.plot(f*fs,20*np.log10(np.abs(H))) 249 | if n == N_filt-1: 250 | plt.xlabel('Frequency (Hz)') 251 | plt.ylabel('Gain (dB)') 252 | plt.title('Frequency Response - Magnitude') 253 | 254 | elif mode.lower() == 'phase': 255 | plt.plot(f*fs,np.angle(H)) 256 | if n == N_filt-1: 257 | plt.xlabel('Frequency (Hz)') 258 | plt.ylabel('Phase (rad)') 259 | plt.title('Frequency Response - Phase') 260 | 261 | elif (mode.lower() == 'groupdelay_s') or (mode.lower() == 'groupdelay_t'): 262 | """ 263 | Notes 264 | ----- 265 | 266 | Since this calculation involves finding the derivative of the 267 | phase response, care must be taken at phase wrapping points 268 | and when the phase jumps by +/-pi, which occurs when the 269 | amplitude response changes sign. Since the amplitude response 270 | is zero when the sign changes, the jumps do not alter the group 271 | delay results. 272 | """ 273 | theta = np.unwrap(np.angle(H)) 274 | # Since theta for an FIR filter is likely to have many pi phase 275 | # jumps too, we unwrap a second time 2*theta and divide by 2 276 | theta2 = np.unwrap(2*theta)/2. 277 | theta_dif = np.diff(theta2) 278 | f_diff = np.diff(f) 279 | Tg = -np.diff(theta2)/np.diff(w) 280 | # For gain almost zero set groupdelay = 0 281 | idx = np.nonzero(np.ravel(20*np.log10(H[:-1]) < -400))[0] 282 | Tg[idx] = np.zeros(len(idx)) 283 | max_Tg = np.max(Tg) 284 | #print(max_Tg) 285 | if mode.lower() == 'groupdelay_t': 286 | max_Tg /= fs 287 | plt.plot(f[:-1]*fs,Tg/fs) 288 | plt.ylim([0,1.2*max_Tg]) 289 | else: 290 | plt.plot(f[:-1]*fs,Tg) 291 | plt.ylim([0,1.2*max_Tg]) 292 | if n == N_filt-1: 293 | plt.xlabel('Frequency (Hz)') 294 | if mode.lower() == 'groupdelay_t': 295 | plt.ylabel('Group Delay (s)') 296 | else: 297 | plt.ylabel('Group Delay (samples)') 298 | plt.title('Frequency Response - Group Delay') 299 | else: 300 | s1 = 'Error, mode must be "dB", "phase, ' 301 | s2 = '"groupdelay_s", or "groupdelay_t"' 302 | log.info(s1 + s2) 303 | 304 | 305 | def freqz_cas(sos,w): 306 | """ 307 | Cascade frequency response 308 | 309 | Mark Wickert October 2016 310 | """ 311 | Ns,Mcol = sos.shape 312 | w,Hcas = signal.freqz(sos[0,:3],sos[0,3:],w) 313 | for k in range(1,Ns): 314 | w,Htemp = signal.freqz(sos[k,:3],sos[k,3:],w) 315 | Hcas *= Htemp 316 | return w, Hcas 317 | 318 | 319 | def freqz_resp_cas_list(sos, mode = 'dB', fs=1.0, n_pts=1024, fsize=(6, 4)): 320 | """ 321 | A method for displaying cascade digital filter form frequency response 322 | magnitude, phase, and group delay. A plot is produced using matplotlib 323 | 324 | freq_resp(self,mode = 'dB',Npts = 1024) 325 | 326 | A method for displaying the filter frequency response magnitude, 327 | phase, and group delay. A plot is produced using matplotlib 328 | 329 | freqz_resp(b,a=[1],mode = 'dB',Npts = 1024,fsize=(6,4)) 330 | 331 | b = ndarray of numerator coefficients 332 | a = ndarray of denominator coefficents 333 | mode = display mode: 'dB' magnitude, 'phase' in radians, or 334 | 'groupdelay_s' in samples and 'groupdelay_t' in sec, 335 | all versus frequency in Hz 336 | Npts = number of points to plot; default is 1024 337 | fsize = figure size; defult is (6,4) inches 338 | 339 | Mark Wickert, January 2015 340 | """ 341 | if type(sos) == list: 342 | # We have a list of filters 343 | N_filt = len(sos) 344 | f = np.arange(0, n_pts) / (2.0 * n_pts) 345 | for n in range(N_filt): 346 | w,H = freqz_cas(sos[n],2*np.pi*f) 347 | if n == 0: 348 | plt.figure(figsize=fsize) 349 | if mode.lower() == 'db': 350 | plt.plot(f*fs,20*np.log10(np.abs(H))) 351 | if n == N_filt-1: 352 | plt.xlabel('Frequency (Hz)') 353 | plt.ylabel('Gain (dB)') 354 | plt.title('Frequency Response - Magnitude') 355 | 356 | elif mode.lower() == 'phase': 357 | plt.plot(f*fs,np.angle(H)) 358 | if n == N_filt-1: 359 | plt.xlabel('Frequency (Hz)') 360 | plt.ylabel('Phase (rad)') 361 | plt.title('Frequency Response - Phase') 362 | 363 | elif (mode.lower() == 'groupdelay_s') or (mode.lower() == 'groupdelay_t'): 364 | """ 365 | Notes 366 | ----- 367 | 368 | Since this calculation involves finding the derivative of the 369 | phase response, care must be taken at phase wrapping points 370 | and when the phase jumps by +/-pi, which occurs when the 371 | amplitude response changes sign. Since the amplitude response 372 | is zero when the sign changes, the jumps do not alter the group 373 | delay results. 374 | """ 375 | theta = np.unwrap(np.angle(H)) 376 | # Since theta for an FIR filter is likely to have many pi phase 377 | # jumps too, we unwrap a second time 2*theta and divide by 2 378 | theta2 = np.unwrap(2*theta)/2. 379 | theta_dif = np.diff(theta2) 380 | f_diff = np.diff(f) 381 | Tg = -np.diff(theta2)/np.diff(w) 382 | # For gain almost zero set groupdelay = 0 383 | idx = np.nonzero(np.ravel(20*np.log10(H[:-1]) < -400))[0] 384 | Tg[idx] = np.zeros(len(idx)) 385 | max_Tg = np.max(Tg) 386 | #print(max_Tg) 387 | if mode.lower() == 'groupdelay_t': 388 | max_Tg /= fs 389 | plt.plot(f[:-1]*fs,Tg/fs) 390 | plt.ylim([0,1.2*max_Tg]) 391 | else: 392 | plt.plot(f[:-1]*fs,Tg) 393 | plt.ylim([0,1.2*max_Tg]) 394 | if n == N_filt-1: 395 | plt.xlabel('Frequency (Hz)') 396 | if mode.lower() == 'groupdelay_t': 397 | plt.ylabel('Group Delay (s)') 398 | else: 399 | plt.ylabel('Group Delay (samples)') 400 | plt.title('Frequency Response - Group Delay') 401 | else: 402 | s1 = 'Error, mode must be "dB", "phase, ' 403 | s2 = '"groupdelay_s", or "groupdelay_t"' 404 | log.info(s1 + s2) 405 | 406 | 407 | def unique_cpx_roots(rlist,tol = 0.001): 408 | """ 409 | 410 | The average of the root values is used when multiplicity 411 | is greater than one. 412 | 413 | Mark Wickert October 2016 414 | """ 415 | uniq = [rlist[0]] 416 | mult = [1] 417 | for k in range(1,len(rlist)): 418 | N_uniq = len(uniq) 419 | for m in range(N_uniq): 420 | if abs(rlist[k]-uniq[m]) <= tol: 421 | mult[m] += 1 422 | uniq[m] = (uniq[m]*(mult[m]-1) + rlist[k])/float(mult[m]) 423 | break 424 | uniq = np.hstack((uniq,rlist[k])) 425 | mult = np.hstack((mult,[1])) 426 | return np.array(uniq), np.array(mult) 427 | 428 | 429 | def sos_cascade(sos1,sos2): 430 | """ 431 | 432 | Mark Wickert October 2016 433 | """ 434 | return np.vstack((sos1,sos2)) 435 | 436 | 437 | def sos_zplane(sos,auto_scale=True,size=2,tol = 0.001): 438 | """ 439 | Create an z-plane pole-zero plot. 440 | 441 | Create an z-plane pole-zero plot using the numerator 442 | and denominator z-domain system function coefficient 443 | ndarrays b and a respectively. Assume descending powers of z. 444 | 445 | Parameters 446 | ---------- 447 | sos : ndarray of the sos coefficients 448 | auto_scale : bool (default True) 449 | size : plot radius maximum when scale = False 450 | 451 | Returns 452 | ------- 453 | (M,N) : tuple of zero and pole counts + plot window 454 | 455 | Notes 456 | ----- 457 | This function tries to identify repeated poles and zeros and will 458 | place the multiplicity number above and to the right of the pole or zero. 459 | The difficulty is setting the tolerance for this detection. Currently it 460 | is set at 1e-3 via the function signal.unique_roots. 461 | 462 | Examples 463 | -------- 464 | >>> # Here the plot is generated using auto_scale 465 | >>> sos_zplane(sos) 466 | >>> # Here the plot is generated using manual scaling 467 | >>> sos_zplane(sos,False,1.5) 468 | """ 469 | Ns,Mcol = sos.shape 470 | # Extract roots from sos num and den removing z = 0 471 | # roots due to first-order sections 472 | N_roots = [] 473 | for k in range(Ns): 474 | N_roots_tmp = np.roots(sos[k,:3]) 475 | if N_roots_tmp[1] == 0.: 476 | N_roots = np.hstack((N_roots,N_roots_tmp[0])) 477 | else: 478 | N_roots = np.hstack((N_roots,N_roots_tmp)) 479 | D_roots = [] 480 | for k in range(Ns): 481 | D_roots_tmp = np.roots(sos[k,3:]) 482 | if D_roots_tmp[1] == 0.: 483 | D_roots = np.hstack((D_roots,D_roots_tmp[0])) 484 | else: 485 | D_roots = np.hstack((D_roots,D_roots_tmp)) 486 | # Plot labels if multiplicity greater than 1 487 | x_scale = 1.5*size 488 | y_scale = 1.5*size 489 | x_off = 0.02 490 | y_off = 0.01 491 | M = len(N_roots) 492 | N = len(D_roots) 493 | if auto_scale: 494 | if M > 0 and N > 0: 495 | size = max(np.max(np.abs(N_roots)),np.max(np.abs(D_roots)))+.1 496 | elif M > 0: 497 | size = max(np.max(np.abs(N_roots)),1.0)+.1 498 | elif N > 0: 499 | size = max(1.0,np.max(np.abs(D_roots)))+.1 500 | else: 501 | size = 1.1 502 | plt.figure(figsize=(5,5)) 503 | plt.axis('equal') 504 | r = np.linspace(0,2*np.pi,200) 505 | plt.plot(np.cos(r),np.sin(r),'r--') 506 | plt.plot([-size,size],[0,0],'k-.') 507 | plt.plot([0,0],[-size,size],'k-.') 508 | if M > 0: 509 | #N_roots = np.roots(b) 510 | N_uniq, N_mult=unique_cpx_roots(N_roots,tol=tol) 511 | plt.plot(np.real(N_uniq),np.imag(N_uniq),'ko',mfc='None',ms=8) 512 | idx_N_mult = np.nonzero(np.ravel(N_mult>1))[0] 513 | for k in range(len(idx_N_mult)): 514 | x_loc = np.real(N_uniq[idx_N_mult[k]]) + x_off*x_scale 515 | y_loc =np.imag(N_uniq[idx_N_mult[k]]) + y_off*y_scale 516 | plt.text(x_loc,y_loc,str(N_mult[idx_N_mult[k]]), 517 | ha='center',va='bottom',fontsize=10) 518 | if N > 0: 519 | #D_roots = np.roots(a) 520 | D_uniq, D_mult=unique_cpx_roots(D_roots,tol=tol) 521 | plt.plot(np.real(D_uniq),np.imag(D_uniq),'kx',ms=8) 522 | idx_D_mult = np.nonzero(np.ravel(D_mult>1))[0] 523 | for k in range(len(idx_D_mult)): 524 | x_loc = np.real(D_uniq[idx_D_mult[k]]) + x_off*x_scale 525 | y_loc =np.imag(D_uniq[idx_D_mult[k]]) + y_off*y_scale 526 | plt.text(x_loc,y_loc,str(D_mult[idx_D_mult[k]]), 527 | ha='center',va='bottom',fontsize=10) 528 | if M - N < 0: 529 | plt.plot(0.0,0.0,'bo',mfc='None',ms=8) 530 | elif M - N > 0: 531 | plt.plot(0.0,0.0,'kx',ms=8) 532 | if abs(M - N) > 1: 533 | plt.text(x_off*x_scale,y_off*y_scale,str(abs(M-N)), 534 | ha='center',va='bottom',fontsize=10) 535 | plt.xlabel('Real Part') 536 | plt.ylabel('Imaginary Part') 537 | plt.title('Pole-Zero Plot') 538 | #plt.grid() 539 | plt.axis([-size,size,-size,size]) 540 | return M,N 541 | -------------------------------------------------------------------------------- /src/sk_dsp_comm/multirate_helper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Multirate help for building interpolation and decimation systems 3 | 4 | Copyright (c) March 2017, Mark Wickert 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | The views and conclusions contained in the software and documentation are those 28 | of the authors and should not be interpreted as representing official policies, 29 | either expressed or implied, of the FreeBSD Project. 30 | """ 31 | 32 | from matplotlib import pylab 33 | import matplotlib.pyplot as plt 34 | import numpy as np 35 | import scipy.signal as signal 36 | from . import sigsys as ssd 37 | from . import fir_design_helper as fir_d 38 | from . import iir_design_helper as iir_d 39 | 40 | from logging import getLogger 41 | log = getLogger(__name__) 42 | import warnings 43 | 44 | 45 | class rate_change(object): 46 | """ 47 | A simple class for encapsulating the upsample/filter and 48 | filter/downsample operations used in modeling a comm 49 | system. Objects of this class will hold the required filter 50 | coefficients once an object is instantiated. 51 | 52 | Mark Wickert February 2015 53 | """ 54 | def __init__(self,M_change = 12,fcutoff=0.9,N_filt_order=8,ftype='butter'): 55 | """ 56 | Object constructor method 57 | """ 58 | self.M = M_change # Rate change factor M or L 59 | self.fc = fcutoff*.5 # must be fs/(2*M), but scale by fcutoff 60 | self.N_forder = N_filt_order 61 | if ftype.lower() == 'butter': 62 | self.b, self.a = signal.butter(self.N_forder,2/self.M*self.fc) 63 | elif ftype.lower() == 'cheby1': 64 | # Set the ripple to 0.05 dB 65 | self.b, self.a = signal.cheby1(self.N_forder,0.05,2/self.M*self.fc) 66 | else: 67 | warnings.warn('ftype must be "butter" or "cheby1"') 68 | 69 | def up(self,x): 70 | """ 71 | Upsample and filter the signal 72 | """ 73 | y = self.M*ssd.upsample(x,self.M) 74 | y = signal.lfilter(self.b,self.a,y) 75 | return y 76 | 77 | def dn(self,x): 78 | """ 79 | Downsample and filter the signal 80 | """ 81 | y = signal.lfilter(self.b,self.a,x) 82 | y = ssd.downsample(y,self.M) 83 | return y 84 | 85 | class multirate_FIR(object): 86 | """ 87 | A simple class for encapsulating FIR filtering, or FIR upsample/ 88 | filter, or FIR filter/downsample operations used in modeling a comm 89 | system. Objects of this class will hold the required filter 90 | coefficients once an object is instantiated. Frequency response 91 | and the pole zero plot can also be plotted using supplied class methods. 92 | 93 | Mark Wickert March 2017 94 | """ 95 | def __init__(self,b): 96 | """ 97 | Object constructor method 98 | """ 99 | self.N_forder = len(b) 100 | self.b = b 101 | log.info('FIR filter taps = %d' % self.N_forder) 102 | 103 | 104 | def filter(self,x): 105 | """ 106 | Filter the signal 107 | """ 108 | y = signal.lfilter(self.b,[1],x) 109 | return y 110 | 111 | 112 | def up(self,x,L_change = 12): 113 | """ 114 | Upsample and filter the signal 115 | """ 116 | y = L_change*ssd.upsample(x,L_change) 117 | y = signal.lfilter(self.b,[1],y) 118 | return y 119 | 120 | 121 | def dn(self,x,M_change = 12): 122 | """ 123 | Downsample and filter the signal 124 | """ 125 | y = signal.lfilter(self.b,[1],x) 126 | y = ssd.downsample(y,M_change) 127 | return y 128 | 129 | 130 | def freq_resp(self, mode= 'dB', fs = 8000, ylim = [-100,2]): 131 | """ 132 | 133 | """ 134 | fir_d.freqz_resp_list([self.b], [1], mode, fs=fs, n_pts= 1024) 135 | pylab.grid() 136 | pylab.ylim(ylim) 137 | 138 | 139 | def zplane(self,auto_scale=True,size=2,detect_mult=True,tol=0.001): 140 | """ 141 | Plot the poles and zeros of the FIR filter in the z-plane 142 | """ 143 | ssd.zplane(self.b,[1],auto_scale,size,tol) 144 | 145 | 146 | class multirate_IIR(object): 147 | """ 148 | A simple class for encapsulating IIR filtering, or IIR upsample/ 149 | filter, or IIR filter/downsample operations used in modeling a comm 150 | system. Objects of this class will hold the required filter 151 | coefficients once an object is instantiated. Frequency response 152 | and the pole zero plot can also be plotted using supplied class methods. 153 | For added robustness to floating point quantization all filtering 154 | is done using the scipy.signal cascade of second-order sections filter 155 | method y = sosfilter(sos,x). 156 | 157 | Mark Wickert March 2017 158 | """ 159 | def __init__(self,sos): 160 | """ 161 | Object constructor method 162 | """ 163 | self.N_forder = np.sum(np.sign(np.abs(sos[:,2]))) \ 164 | + np.sum(np.sign(np.abs(sos[:,1]))) 165 | self.sos = sos 166 | log.info('IIR filter order = %d' % self.N_forder) 167 | 168 | 169 | def filter(self,x): 170 | """ 171 | Filter the signal using second-order sections 172 | """ 173 | y = signal.sosfilt(self.sos,x) 174 | return y 175 | 176 | 177 | def up(self,x,L_change = 12): 178 | """ 179 | Upsample and filter the signal 180 | """ 181 | y = L_change*ssd.upsample(x,L_change) 182 | y = signal.sosfilt(self.sos,y) 183 | return y 184 | 185 | 186 | def dn(self,x,M_change = 12): 187 | """ 188 | Downsample and filter the signal 189 | """ 190 | y = signal.sosfilt(self.sos,x) 191 | y = ssd.downsample(y,M_change) 192 | return y 193 | 194 | 195 | def freq_resp(self, mode= 'dB', fs = 8000, ylim = [-100,2]): 196 | """ 197 | Frequency response plot 198 | """ 199 | iir_d.freqz_resp_cas_list([self.sos],mode,fs=fs) 200 | pylab.grid() 201 | pylab.ylim(ylim) 202 | 203 | 204 | def zplane(self,auto_scale=True,size=2,detect_mult=True,tol=0.001): 205 | """ 206 | Plot the poles and zeros of the FIR filter in the z-plane 207 | """ 208 | iir_d.sos_zplane(self.sos,auto_scale,size,tol) 209 | 210 | 211 | def freqz_resp(b,a=[1],mode = 'dB',fs=1.0,Npts = 1024,fsize=(6,4)): 212 | """ 213 | A method for displaying digital filter frequency response magnitude, 214 | phase, and group delay. A plot is produced using matplotlib 215 | 216 | freq_resp(self,mode = 'dB',Npts = 1024) 217 | 218 | A method for displaying the filter frequency response magnitude, 219 | phase, and group delay. A plot is produced using matplotlib 220 | 221 | freqz_resp(b,a=[1],mode = 'dB',Npts = 1024,fsize=(6,4)) 222 | 223 | b = ndarray of numerator coefficients 224 | a = ndarray of denominator coefficents 225 | mode = display mode: 'dB' magnitude, 'phase' in radians, or 226 | 'groupdelay_s' in samples and 'groupdelay_t' in sec, 227 | all versus frequency in Hz 228 | Npts = number of points to plot; defult is 1024 229 | fsize = figure size; defult is (6,4) inches 230 | 231 | Mark Wickert, January 2015 232 | """ 233 | f = np.arange(0,Npts)/(2.0*Npts) 234 | w,H = signal.freqz(b,a,2*np.pi*f) 235 | plt.figure(figsize=fsize) 236 | if mode.lower() == 'db': 237 | plt.plot(f*fs,20*np.log10(np.abs(H))) 238 | plt.xlabel('Frequency (Hz)') 239 | plt.ylabel('Gain (dB)') 240 | plt.title('Frequency Response - Magnitude') 241 | 242 | elif mode.lower() == 'phase': 243 | plt.plot(f*fs,np.angle(H)) 244 | plt.xlabel('Frequency (Hz)') 245 | plt.ylabel('Phase (rad)') 246 | plt.title('Frequency Response - Phase') 247 | 248 | elif (mode.lower() == 'groupdelay_s') or (mode.lower() == 'groupdelay_t'): 249 | """ 250 | Notes 251 | ----- 252 | 253 | Since this calculation involves finding the derivative of the 254 | phase response, care must be taken at phase wrapping points 255 | and when the phase jumps by +/-pi, which occurs when the 256 | amplitude response changes sign. Since the amplitude response 257 | is zero when the sign changes, the jumps do not alter the group 258 | delay results. 259 | """ 260 | theta = np.unwrap(np.angle(H)) 261 | # Since theta for an FIR filter is likely to have many pi phase 262 | # jumps too, we unwrap a second time 2*theta and divide by 2 263 | theta2 = np.unwrap(2*theta)/2. 264 | theta_dif = np.diff(theta2) 265 | f_diff = np.diff(f) 266 | Tg = -np.diff(theta2)/np.diff(w) 267 | # For gain almost zero set groupdelay = 0 268 | idx = pylab.find(20*np.log10(H[:-1]) < -400) 269 | Tg[idx] = np.zeros(len(idx)) 270 | max_Tg = np.max(Tg) 271 | #print(max_Tg) 272 | if mode.lower() == 'groupdelay_t': 273 | max_Tg /= fs 274 | plt.plot(f[:-1]*fs,Tg/fs) 275 | plt.ylim([0,1.2*max_Tg]) 276 | else: 277 | plt.plot(f[:-1]*fs,Tg) 278 | plt.ylim([0,1.2*max_Tg]) 279 | plt.xlabel('Frequency (Hz)') 280 | if mode.lower() == 'groupdelay_t': 281 | plt.ylabel('Group Delay (s)') 282 | else: 283 | plt.ylabel('Group Delay (samples)') 284 | plt.title('Frequency Response - Group Delay') 285 | else: 286 | s1 = 'Error, mode must be "dB", "phase, ' 287 | s2 = '"groupdelay_s", or "groupdelay_t"' 288 | warnings.warn(s1 + s2) 289 | 290 | -------------------------------------------------------------------------------- /src/sk_dsp_comm/synchronization.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Digital Communications Synchronization 3 | and PLLs Function Module 4 | 5 | A collection of useful functions when studying PLLs 6 | and synchronization and digital comm 7 | 8 | Copyright (c) March 2017, Mark Wickert 9 | All rights reserved. 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions are met: 13 | 14 | 1. Redistributions of source code must retain the above copyright notice, this 15 | list of conditions and the following disclaimer. 16 | 2. Redistributions in binary form must reproduce the above copyright notice, 17 | this list of conditions and the following disclaimer in the documentation 18 | and/or other materials provided with the distribution. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | The views and conclusions contained in the software and documentation are those 32 | of the authors and should not be interpreted as representing official policies, 33 | either expressed or implied, of the FreeBSD Project. 34 | """ 35 | 36 | import numpy as np 37 | from logging import getLogger 38 | log = getLogger(__name__) 39 | import warnings 40 | 41 | 42 | def NDA_symb_sync(z,Ns,L,BnTs,zeta=0.707,I_ord=3): 43 | """ 44 | zz,e_tau = NDA_symb_sync(z,Ns,L,BnTs,zeta=0.707,I_ord=3) 45 | 46 | z = complex baseband input signal at nominally Ns samples 47 | per symbol 48 | Ns = Nominal number of samples per symbol (Ts/T) in the symbol 49 | tracking loop, often 4 50 | BnTs = time bandwidth product of loop bandwidth and the symbol period, 51 | thus the loop bandwidth as a fraction of the symbol rate. 52 | zeta = loop damping factor 53 | I_ord = interpolator order, 1, 2, or 3 54 | 55 | e_tau = the timing error e(k) input to the loop filter 56 | 57 | Kp = The phase detector gain in the symbol tracking loop; for the 58 | NDA algoithm used here always 1 59 | 60 | Mark Wickert July 2014 61 | 62 | Motivated by code found in M. Rice, Digital Communications A Discrete-Time 63 | Approach, Prentice Hall, New Jersey, 2009. (ISBN 978-0-13-030497-1). 64 | """ 65 | # Loop filter parameters 66 | K0 = -1.0 # The modulo 1 counter counts down so a sign change in loop 67 | Kp = 1.0 68 | K1 = 4*zeta/(zeta + 1/(4*zeta))*BnTs/Ns/Kp/K0 69 | K2 = 4/(zeta + 1/(4*zeta))**2*(BnTs/Ns)**2/Kp/K0 70 | zz = np.zeros(len(z),dtype=np.complex128) 71 | #zz = np.zeros(int(np.floor(len(z)/float(Ns))),dtype=np.complex128) 72 | e_tau = np.zeros(len(z)) 73 | #e_tau = np.zeros(int(np.floor(len(z)/float(Ns)))) 74 | #z_TED_buff = np.zeros(Ns) 75 | c1_buff = np.zeros(2*L+1) 76 | 77 | vi = 0 78 | CNT_next = 0 79 | mu_next = 0 80 | underflow = 0 81 | epsilon = 0 82 | mm = 1 83 | z = np.hstack(([0], z)) 84 | for nn in range(1,Ns*int(np.floor(len(z)/float(Ns)-(Ns-1)))): 85 | # Define variables used in linear interpolator control 86 | CNT = CNT_next 87 | mu = mu_next 88 | if underflow == 1: 89 | if I_ord == 1: 90 | # Decimated interpolator output (piecewise linear) 91 | z_interp = mu*z[nn] + (1 - mu)*z[nn-1] 92 | elif I_ord == 2: 93 | # Decimated interpolator output (piecewise parabolic) 94 | # in Farrow form with alpha = 1/2 95 | v2 = 1/2.*np.sum(z[nn+2:nn-1-1:-1]*[1, -1, -1, 1]) 96 | v1 = 1/2.*np.sum(z[nn+2:nn-1-1:-1]*[-1, 3, -1, -1]) 97 | v0 = z[nn] 98 | z_interp = (mu*v2 + v1)*mu + v0 99 | elif I_ord == 3: 100 | # Decimated interpolator output (piecewise cubic) 101 | # in Farrow form 102 | v3 = np.sum(z[nn+2:nn-1-1:-1]*[1/6., -1/2., 1/2., -1/6.]) 103 | v2 = np.sum(z[nn+2:nn-1-1:-1]*[0, 1/2., -1, 1/2.]) 104 | v1 = np.sum(z[nn+2:nn-1-1:-1]*[-1/6., 1, -1/2., -1/3.]) 105 | v0 = z[nn] 106 | z_interp = ((mu*v3 + v2)*mu + v1)*mu + v0 107 | else: 108 | log.error('I_ord must 1, 2, or 3') 109 | # Form TED output that is smoothed using 2*L+1 samples 110 | # We need Ns interpolants for this TED: 0:Ns-1 111 | c1 = 0 112 | for kk in range(Ns): 113 | if I_ord == 1: 114 | # piecewise linear interp over Ns samples for TED 115 | z_TED_interp = mu*z[nn+kk] + (1 - mu)*z[nn-1+kk] 116 | elif I_ord == 2: 117 | # piecewise parabolic in Farrow form with alpha = 1/2 118 | v2 = 1/2.*np.sum(z[nn+kk+2:nn+kk-1-1:-1]*[1, -1, -1, 1]) 119 | v1 = 1/2.*np.sum(z[nn+kk+2:nn+kk-1-1:-1]*[-1, 3, -1, -1]) 120 | v0 = z[nn+kk] 121 | z_TED_interp = (mu*v2 + v1)*mu + v0 122 | elif I_ord == 3: 123 | # piecewise cubic in Farrow form 124 | v3 = np.sum(z[nn+kk+2:nn+kk-1-1:-1]*[1/6., -1/2., 1/2., -1/6.]) 125 | v2 = np.sum(z[nn+kk+2:nn+kk-1-1:-1]*[0, 1/2., -1, 1/2.]) 126 | v1 = np.sum(z[nn+kk+2:nn+kk-1-1:-1]*[-1/6., 1, -1/2., -1/3.]) 127 | v0 = z[nn+kk] 128 | z_TED_interp = ((mu*v3 + v2)*mu + v1)*mu + v0 129 | else: 130 | log.error('Error: I_ord must 1, 2, or 3') 131 | c1 = c1 + np.abs(z_TED_interp)**2 * np.exp(-1j*2*np.pi/Ns*kk) 132 | c1 = c1/Ns 133 | # Update 2*L+1 length buffer for TED output smoothing 134 | c1_buff = np.hstack(([c1], c1_buff[:-1])) 135 | # Form the smoothed TED output 136 | epsilon = -1/(2*np.pi)*np.angle(np.sum(c1_buff)/(2*L+1)) 137 | # Save symbol spaced (decimated to symbol rate) interpolants in zz 138 | zz[mm] = z_interp 139 | e_tau[mm] = epsilon # log the error to the output vector e 140 | mm += 1 141 | else: 142 | # Simple zezo-order hold interpolation between symbol samples 143 | # we just coast using the old value 144 | #epsilon = 0 145 | pass 146 | vp = K1*epsilon # proportional component of loop filter 147 | vi = vi + K2*epsilon # integrator component of loop filter 148 | v = vp + vi # loop filter output 149 | W = 1/float(Ns) + v # counter control word 150 | 151 | # update registers 152 | CNT_next = CNT - W # Update counter value for next cycle 153 | if CNT_next < 0: # Test to see if underflow has occured 154 | CNT_next = 1 + CNT_next # Reduce counter value modulo-1 if underflow 155 | underflow = 1 # Set the underflow flag 156 | mu_next = CNT/W # update mu 157 | else: 158 | underflow = 0 159 | mu_next = mu 160 | # Remove zero samples at end 161 | zz = zz[:-(len(zz)-mm+1)] 162 | # Normalize so symbol values have a unity magnitude 163 | zz /=np.std(zz) 164 | e_tau = e_tau[:-(len(e_tau)-mm+1)] 165 | return zz, e_tau 166 | 167 | 168 | def DD_carrier_sync(z, M, BnTs, zeta=0.707, mod_type = 'MPSK', type = 0, open_loop = False): 169 | """ 170 | z_prime,a_hat,e_phi = DD_carrier_sync(z,M,BnTs,zeta=0.707,type=0) 171 | Decision directed carrier phase tracking 172 | 173 | z = complex baseband PSK signal at one sample per symbol 174 | M = The PSK modulation order, i.e., 2, 8, or 8. 175 | BnTs = time bandwidth product of loop bandwidth and the symbol period, 176 | thus the loop bandwidth as a fraction of the symbol rate. 177 | zeta = loop damping factor 178 | type = Phase error detector type: 0 <> ML, 1 <> heuristic 179 | 180 | z_prime = phase rotation output (like soft symbol values) 181 | a_hat = the hard decision symbol values landing at the constellation 182 | values 183 | e_phi = the phase error e(k) into the loop filter 184 | 185 | Ns = Nominal number of samples per symbol (Ts/T) in the carrier 186 | phase tracking loop, almost always 1 187 | Kp = The phase detector gain in the carrier phase tracking loop; 188 | This value depends upon the algorithm type. For the ML scheme 189 | described at the end of notes Chapter 9, A = 1, K 1/sqrt(2), 190 | so Kp = sqrt(2). 191 | 192 | Mark Wickert July 2014 193 | Updated for improved MPSK performance April 2020 194 | Added experimental MQAM capability April 2020 195 | 196 | Motivated by code found in M. Rice, Digital Communications A Discrete-Time 197 | Approach, Prentice Hall, New Jersey, 2009. (ISBN 978-0-13-030497-1). 198 | """ 199 | Ns = 1 200 | z_prime = np.zeros_like(z) 201 | a_hat = np.zeros_like(z) 202 | e_phi = np.zeros(len(z)) 203 | theta_h = np.zeros(len(z)) 204 | theta_hat = 0 205 | 206 | # Tracking loop constants 207 | Kp = 1 # What is it for the different schemes and modes? 208 | K0 = 1 209 | K1 = 4*zeta/(zeta + 1/(4*zeta))*BnTs/Ns/Kp/K0; 210 | K2 = 4/(zeta + 1/(4*zeta))**2*(BnTs/Ns)**2/Kp/K0; 211 | 212 | # Initial condition 213 | vi = 0 214 | # Scaling for MQAM using signal power 215 | # and known relationship for QAM. 216 | if mod_type == 'MQAM': 217 | z_scale = np.std(z) * np.sqrt(3/(2*(M-1))) 218 | z = z/z_scale 219 | for nn in range(len(z)): 220 | # Multiply by the phase estimate exp(-j*theta_hat[n]) 221 | z_prime[nn] = z[nn]*np.exp(-1j*theta_hat) 222 | if mod_type == 'MPSK': 223 | if M == 2: 224 | a_hat[nn] = np.sign(z_prime[nn].real) + 1j*0 225 | elif M == 4: 226 | a_hat[nn] = (np.sign(z_prime[nn].real) + \ 227 | 1j*np.sign(z_prime[nn].imag))/np.sqrt(2) 228 | elif M > 4: 229 | # round to the nearest integer and fold to nonnegative 230 | # integers; detection into M-levels with thresholds at mid points. 231 | a_hat[nn] = np.mod((np.rint(np.angle(z_prime[nn])*M/2/np.pi)).astype(np.int),M) 232 | a_hat[nn] = np.exp(1j*2*np.pi*a_hat[nn]/M) 233 | else: 234 | print('M must be 2, 4, 8, etc.') 235 | elif mod_type == 'MQAM': 236 | # Scale adaptively assuming var(x_hat) is proportional to 237 | if M ==2 or M == 4 or M == 16 or M == 64 or M == 256: 238 | x_m = np.sqrt(M)-1 239 | if M == 2: x_m = 1 240 | # Shift to quadrant one for hard decisions 241 | a_hat_shift = (z_prime[nn] + x_m*(1+1j))/2 242 | # Soft IQ symbol values are converted to hard symbol decisions 243 | a_hat_shiftI = np.int16(np.clip(np.rint(a_hat_shift.real),0,x_m)) 244 | a_hat_shiftQ = np.int16(np.clip(np.rint(a_hat_shift.imag),0,x_m)) 245 | # Shift back to antipodal QAM 246 | a_hat[nn] = 2*(a_hat_shiftI + 1j*a_hat_shiftQ) - x_m*(1+1j) 247 | else: 248 | print('M must be 2, 4, 16, 64, or 256'); 249 | if type == 0: 250 | # Maximum likelihood (ML) Rice 251 | e_phi[nn] = z_prime[nn].imag * a_hat[nn].real - \ 252 | z_prime[nn].real * a_hat[nn].imag 253 | elif type == 1: 254 | # Heuristic Rice 255 | e_phi[nn] = np.angle(z_prime[nn]) - np.angle(a_hat[nn]) 256 | # Wrap the phase to [-pi,pi] 257 | e_phi[nn] = np.angle(np.exp(1j*e_phi[nn])) 258 | elif type == 2: 259 | # Ouyang and Wang 2002 MQAM paper 260 | e_phi[nn] = imag(z_prime[nn]/a_hat[nn]) 261 | else: 262 | print('Type must be 0 or 1') 263 | vp = K1*e_phi[nn] # proportional component of loop filter 264 | vi = vi + K2*e_phi[nn] # integrator component of loop filter 265 | v = vp + vi # loop filter output 266 | theta_hat = np.mod(theta_hat + v,2*np.pi) 267 | theta_h[nn] = theta_hat # phase track output array 268 | if open_loop: 269 | theta_hat = 0 # for open-loop testing 270 | 271 | # Normalize MQAM outputs 272 | if mod_type == 'MQAM': 273 | z_prime *= z_scale 274 | return z_prime, a_hat, e_phi, theta_h 275 | 276 | 277 | def time_step(z, ns, t_step, n_step): 278 | """ 279 | Create a one sample per symbol signal containing a phase rotation 280 | step Nsymb into the waveform. 281 | 282 | :param z: complex baseband signal after matched filter 283 | :param ns: number of sample per symbol 284 | :param t_step: in samples relative to Ns 285 | :param n_step: symbol sample location where the step turns on 286 | :return: the one sample per symbol signal containing the phase step 287 | 288 | Mark Wickert July 2014 289 | """ 290 | z_step = np.hstack((z[:ns * n_step], z[(ns * n_step + t_step):], np.zeros(t_step))) 291 | return z_step 292 | 293 | 294 | def phase_step(z, ns, p_step, n_step): 295 | """ 296 | Create a one sample per symbol signal containing a phase rotation 297 | step Nsymb into the waveform. 298 | 299 | :param z: complex baseband signal after matched filter 300 | :param ns: number of sample per symbol 301 | :param p_step: size in radians of the phase step 302 | :param n_step: symbol sample location where the step turns on 303 | :return: the one sample symbol signal containing the phase step 304 | 305 | Mark Wickert July 2014 306 | """ 307 | nn = np.arange(0, len(z[::ns])) 308 | theta = np.zeros(len(nn)) 309 | idx = np.where(nn >= n_step) 310 | theta[idx] = p_step*np.ones(len(idx)) 311 | z_rot = z[::ns] * np.exp(1j * theta) 312 | return z_rot 313 | 314 | 315 | def PLL1(theta,fs,loop_type,Kv,fn,zeta,non_lin): 316 | """ 317 | Baseband Analog PLL Simulation Model 318 | 319 | :param theta: input phase deviation in radians 320 | :param fs: sampling rate in sample per second or Hz 321 | :param loop_type: 1, first-order loop filter F(s)=K_LF; 2, integrator 322 | with lead compensation F(s) = (1 + s tau2)/(s tau1), 323 | i.e., a type II, or 3, lowpass with lead compensation 324 | F(s) = (1 + s tau2)/(1 + s tau1) 325 | :param Kv: VCO gain in Hz/v; note presently assume Kp = 1v/rad 326 | and K_LF = 1; the user can easily change this 327 | :param fn: Loop natural frequency (loops 2 & 3) or cutoff 328 | frquency (loop 1) 329 | :param zeta: Damping factor for loops 2 & 3 330 | :param non_lin: 0, linear phase detector; 1, sinusoidal phase detector 331 | :return: theta_hat = Output phase estimate of the input theta in radians, 332 | ev = VCO control voltage, 333 | phi = phase error = theta - theta_hat 334 | 335 | Notes 336 | ----- 337 | Alternate input in place of natural frequency, fn, in Hz is 338 | the noise equivalent bandwidth Bn in Hz. 339 | 340 | 341 | Mark Wickert, April 2007 for ECE 5625/4625 342 | Modified February 2008 and July 2014 for ECE 5675/4675 343 | Python version August 2014 344 | """ 345 | T = 1/float(fs) 346 | Kv = 2*np.pi*Kv # convert Kv in Hz/v to rad/s/v 347 | 348 | if loop_type == 1: 349 | # First-order loop parameters 350 | # Note Bn = K/4 Hz but K has units of rad/s 351 | #fn = 4*Bn/(2*pi); 352 | K = 2*np.pi*fn # loop natural frequency in rad/s 353 | elif loop_type == 2: 354 | # Second-order loop parameters 355 | #fn = 1/(2*pi) * 2*Bn/(zeta + 1/(4*zeta)); 356 | K = 4 *np.pi*zeta*fn # loop natural frequency in rad/s 357 | tau2 = zeta/(np.pi*fn) 358 | elif loop_type == 3: 359 | # Second-order loop parameters for one-pole lowpass with 360 | # phase lead correction. 361 | #fn = 1/(2*pi) * 2*Bn/(zeta + 1/(4*zeta)); 362 | K = Kv # Essentially the VCO gain sets the single-sided 363 | # hold-in range in Hz, as it is assumed that Kp = 1 364 | # and KLF = 1. 365 | tau1 = K/((2*np.pi*fn)**2) 366 | tau2 = 2*zeta/(2*np.pi*fn)*(1 - 2*np.pi*fn/K*1/(2*zeta)) 367 | else: 368 | warnings.warn('Loop type must be 1, 2, or 3') 369 | 370 | # Initialize integration approximation filters 371 | filt_in_last = 0; filt_out_last = 0; 372 | vco_in_last = 0; vco_out = 0; vco_out_last = 0; 373 | 374 | # Initialize working and final output vectors 375 | n = np.arange(len(theta)) 376 | theta_hat = np.zeros_like(theta) 377 | ev = np.zeros_like(theta) 378 | phi = np.zeros_like(theta) 379 | 380 | # Begin the simulation loop 381 | for k in range(len(n)): 382 | phi[k] = theta[k] - vco_out 383 | if non_lin == 1: 384 | # sinusoidal phase detector 385 | pd_out = np.sin(phi[k]) 386 | else: 387 | # Linear phase detector 388 | pd_out = phi[k] 389 | # Loop gain 390 | gain_out = K/Kv*pd_out # apply VCO gain at VCO 391 | # Loop filter 392 | if loop_type == 2: 393 | filt_in = (1/tau2)*gain_out 394 | filt_out = filt_out_last + T/2*(filt_in + filt_in_last) 395 | filt_in_last = filt_in 396 | filt_out_last = filt_out 397 | filt_out = filt_out + gain_out 398 | elif loop_type == 3: 399 | filt_in = (tau2/tau1)*gain_out - (1/tau1)*filt_out_last 400 | u3 = filt_in + (1/tau2)*filt_out_last 401 | filt_out = filt_out_last + T/2*(filt_in + filt_in_last) 402 | filt_in_last = filt_in 403 | filt_out_last = filt_out 404 | else: 405 | filt_out = gain_out; 406 | # VCO 407 | vco_in = filt_out 408 | if loop_type == 3: 409 | vco_in = u3 410 | vco_out = vco_out_last + T/2*(vco_in + vco_in_last) 411 | vco_in_last = vco_in 412 | vco_out_last = vco_out 413 | vco_out = Kv*vco_out # apply Kv 414 | # Measured loop signals 415 | ev[k] = vco_in 416 | theta_hat[k] = vco_out 417 | return theta_hat, ev, phi 418 | 419 | 420 | def PLL_cbb(x,fs,loop_type,Kv,fn,zeta): 421 | """ 422 | Baseband Analog PLL Simulation Model 423 | 424 | :param x: input phase deviation in radians 425 | :param fs: sampling rate in sample per second or Hz 426 | :param loop_type: 1, first-order loop filter F(s)=K_LF; 2, integrator 427 | with lead compensation F(s) = (1 + s tau2)/(s tau1), 428 | i.e., a type II, or 3, lowpass with lead compensation 429 | F(s) = (1 + s tau2)/(1 + s tau1) 430 | :param Kv: VCO gain in Hz/v; note presently assume Kp = 1v/rad 431 | and K_LF = 1; the user can easily change this 432 | :param fn: Loop natural frequency (loops 2 & 3) or cutoff 433 | frequency (loop 1) 434 | :param zeta: Damping factor for loops 2 & 3 435 | :return: theta_hat = Output phase estimate of the input theta in radians, 436 | ev = VCO control voltage, 437 | phi = phase error = theta - theta_hat 438 | 439 | Mark Wickert, April 2007 for ECE 5625/4625 440 | Modified February 2008 and July 2014 for ECE 5675/4675 441 | Python version August 2014 442 | """ 443 | T = 1/float(fs) 444 | Kv = 2*np.pi*Kv # convert Kv in Hz/v to rad/s/v 445 | 446 | if loop_type == 1: 447 | # First-order loop parameters 448 | # Note Bn = K/4 Hz but K has units of rad/s 449 | #fn = 4*Bn/(2*pi); 450 | K = 2*np.pi*fn # loop natural frequency in rad/s 451 | elif loop_type == 2: 452 | # Second-order loop parameters 453 | #fn = 1/(2*pi) * 2*Bn/(zeta + 1/(4*zeta)); 454 | K = 4 *np.pi*zeta*fn # loop natural frequency in rad/s 455 | tau2 = zeta/(np.pi*fn) 456 | elif loop_type == 3: 457 | # Second-order loop parameters for one-pole lowpass with 458 | # phase lead correction. 459 | #fn = 1/(2*pi) * 2*Bn/(zeta + 1/(4*zeta)); 460 | K = Kv # Essentially the VCO gain sets the single-sided 461 | # hold-in range in Hz, as it is assumed that Kp = 1 462 | # and KLF = 1. 463 | tau1 = K/((2*np.pi*fn)^2); 464 | tau2 = 2*zeta/(2*np.pi*fn)*(1 - 2*np.pi*fn/K*1/(2*zeta)) 465 | else: 466 | warnings.warn('Loop type must be 1, 2, or 3') 467 | 468 | # Initialize integration approximation filters 469 | filt_in_last = 0; filt_out_last = 0; 470 | vco_in_last = 0; vco_out = 0; vco_out_last = 0; 471 | vco_out_cbb = 0 472 | 473 | # Initialize working and final output vectors 474 | n = np.arange(len(x)) 475 | theta_hat = np.zeros(len(x)) 476 | ev = np.zeros(len(x)) 477 | phi = np.zeros(len(x)) 478 | 479 | # Begin the simulation loop 480 | for k in range(len(n)): 481 | #phi[k] = theta[k] - vco_out 482 | phi[k] = np.imag(x[k] * np.conj(vco_out_cbb)) 483 | pd_out = phi[k] 484 | # Loop gain 485 | gain_out = K/Kv*pd_out # apply VCO gain at VCO 486 | # Loop filter 487 | if loop_type == 2: 488 | filt_in = (1/tau2)*gain_out 489 | filt_out = filt_out_last + T/2*(filt_in + filt_in_last) 490 | filt_in_last = filt_in 491 | filt_out_last = filt_out 492 | filt_out = filt_out + gain_out 493 | elif loop_type == 3: 494 | filt_in = (tau2/tau1)*gain_out - (1/tau1)*filt_out_last 495 | u3 = filt_in + (1/tau2)*filt_out_last 496 | filt_out = filt_out_last + T/2*(filt_in + filt_in_last) 497 | filt_in_last = filt_in 498 | filt_out_last = filt_out 499 | else: 500 | filt_out = gain_out; 501 | # VCO 502 | vco_in = filt_out 503 | if loop_type == 3: 504 | vco_in = u3 505 | vco_out = vco_out_last + T/2*(vco_in + vco_in_last) 506 | vco_in_last = vco_in 507 | vco_out_last = vco_out 508 | vco_out = Kv*vco_out # apply Kv 509 | vco_out_cbb = np.exp(1j*vco_out) 510 | # Measured loop signals 511 | ev[k] = vco_in 512 | theta_hat[k] = vco_out 513 | return theta_hat, ev, phi 514 | 515 | -------------------------------------------------------------------------------- /tests/CA_1.h: -------------------------------------------------------------------------------- 1 | //define a CA code 2 | 3 | #include 4 | 5 | #ifndef N_CA 6 | #define N_CA 1023 7 | #endif 8 | /*******************************************************************/ 9 | /* 1023 Bit CA Gold Code 1 */ 10 | int8_t ca1[N_CA] = {1,1,0,0,1,0,0,0,0,0,1,1,1,0,0,1,0,1,0,0,1,0,0, 11 | 1,1,1,1,0,0,1,0,1,0,0,0,1,0,0,1,1,1,1,1,0,1,0, 12 | 1,0,1,1,0,1,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1,1, 13 | 0,0,1,0,0,0,1,1,1,1,0,1,0,0,1,1,1,1,1,1,0,1,1, 14 | 0,1,1,1,0,0,1,1,0,1,1,1,1,1,0,0,1,0,1,0,1,0,1, 15 | 0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,1, 16 | 0,0,0,1,0,0,1,1,0,1,1,1,1,0,0,0,0,0,1,1,1,1,0, 17 | 1,0,1,1,1,0,0,1,1,0,0,1,1,1,1,0,1,1,0,0,0,0,0, 18 | 0,0,1,0,1,1,1,1,0,0,1,1,1,1,1,0,1,0,1,0,0,1,1, 19 | 0,0,0,1,0,1,1,0,1,1,1,0,0,0,1,1,0,1,1,1,1,0,1, 20 | 0,1,0,0,0,1,0,1,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0, 21 | 0,0,1,0,0,0,0,0,0,1,1,0,0,0,1,1,1,0,1,1,0,0,0, 22 | 0,0,0,1,1,1,0,0,0,1,1,0,1,1,1,1,1,1,1,1,1,0,1, 23 | 0,0,1,1,1,0,1,0,0,1,0,1,1,0,1,1,0,0,0,0,1,0,1, 24 | 0,1,0,1,1,0,0,0,1,0,0,1,1,1,0,0,1,0,1,1,0,1,1, 25 | 1,0,1,1,0,0,0,1,1,1,0,1,1,1,0,1,1,1,1,0,0,0,0, 26 | 1,1,0,1,1,0,0,0,0,1,1,0,0,1,0,0,1,0,0,1,0,0,0, 27 | 0,0,1,1,0,1,1,0,1,0,0,1,0,1,1,0,1,1,1,1,0,0,0, 28 | 1,0,1,1,1,0,0,0,0,0,0,1,0,1,0,0,1,0,0,1,1,1,1, 29 | 1,1,0,0,0,0,0,1,0,1,0,1,0,1,1,1,0,0,1,1,1,1,1, 30 | 0,1,0,1,1,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1,1,1,0, 31 | 0,0,1,1,0,1,1,0,1,0,1,0,1,0,1,1,0,1,1,0,0,0,1, 32 | 1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,0,1, 33 | 1,0,0,1,1,0,1,1,0,0,1,1,1,0,1,1,0,1,0,0,0,0,0, 34 | 1,0,1,0,1,0,1,1,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0, 35 | 0,0,1,1,1,0,0,1,1,1,0,0,0,1,0,0,1,0,1,0,0,0,1, 36 | 0,1,0,0,1,0,1,1,0,1,0,0,0,0,1,0,1,0,1,1,0,1,1, 37 | 0,1,0,1,1,0,1,1,0,0,0,1,1,1,0,0,1,1,1,1,0,1,1, 38 | 0,0,1,0,0,0,0,1,1,1,1,1,1,0,0,1,0,1,1,0,1,0,0, 39 | 0,1,0,0,0,0,1,1,1,1,1,0,1,0,1,0,1,1,1,0,0,1,1, 40 | 0,0,1,0,0,1,0,0,1,0,0,1,0,1,1,1,1,1,1,1,1,1,1, 41 | 0,0,0,0,1,1,1,1,1,0,1,1,1,1,0,0,0,1,1,0,1,1,1, 42 | 0,0,1,0,1,1,0,0,0,0,1,1,1,0,0,1,0,1,0,1,0,0,0, 43 | 0,1,0,1,0,0,1,0,1,0,1,1,1,1,1,1,0,0,0,1,1,1,1, 44 | 0,1,1,0,1,0,0,1,1,1,0,1,1,0,0,1,1,1,1,1,1,0,1, 45 | 1,1,1,1,0,1,0,0,0,1,1,0,0,0,1,1,1,1,1,0,0,0,0, 46 | 0,0,0,1,0,0,1,0,1,0,0,0,1,0,1,1,0,1,0,0,0,1,0, 47 | 0,0,1,0,0,1,1,0,1,1,0,0,0,0,0,0,1,1,1,0,1,1,0, 48 | 1,0,0,0,1,1,0,1,0,0,0,1,0,0,1,0,0,0,1,1,1,0,0, 49 | 0,1,0,1,1,0,0,1,1,0,0,1,0,0,1,1,1,1,0,0,1,1,0, 50 | 1,1,1,1,1,1,0,0,1,1,0,0,1,0,1,0,0,1,1,0,1,0,0, 51 | 1,1,0,1,0,1,1,1,1,0,0,1,1,0,1,1,0,1,0,1,0,0,1, 52 | 1,1,0,1,1,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0,0,0, 53 | 0,1,0,0,0,1,0,0,1,0,0,1,1,1,0,0,0,0,1,1,1,0,0, 54 | 1,0,1,0,0,0,1,0,0,0,0}; 55 | /*******************************************************************/ 56 | -------------------------------------------------------------------------------- /tests/CA_12.h: -------------------------------------------------------------------------------- 1 | //define a CA code 2 | 3 | #include 4 | 5 | #ifndef N_CA 6 | #define N_CA 1023 7 | #endif 8 | /*******************************************************************/ 9 | /* 1023 Bit CA Gold Code 12 */ 10 | int8_t ca12[N_CA] = {1,1,1,1,1,0,1,0,0,0,0,1,1,0,1,0,1,1,1,0,0,0,1, 11 | 0,0,1,0,0,0,0,1,0,0,0,1,1,1,1,0,0,0,0,1,0,1,1, 12 | 1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,0,1,0,0,1,0,0,1, 13 | 1,1,1,0,1,0,0,0,0,1,1,1,1,1,1,1,0,1,1,0,0,0,1, 14 | 0,1,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,0,1,1,1,1, 15 | 0,0,1,1,0,0,0,0,0,1,0,1,0,0,1,1,1,1,0,1,1,0,0, 16 | 0,0,0,1,0,0,0,1,0,1,1,1,0,0,0,0,1,0,1,0,0,1,0, 17 | 0,1,0,1,0,1,0,1,0,0,1,1,0,0,1,1,0,1,1,1,1,1,1, 18 | 0,1,1,0,1,1,0,1,1,1,1,0,1,0,0,1,1,1,0,0,0,1,1, 19 | 0,1,0,0,1,1,1,0,1,1,1,0,0,0,0,1,1,0,0,1,0,1,0, 20 | 0,0,1,1,1,0,0,1,0,0,0,1,1,1,0,0,1,0,1,0,0,0,0, 21 | 1,1,0,1,1,1,1,1,0,0,0,1,0,0,1,1,0,1,0,0,0,1,0, 22 | 0,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,0,1,1,0,1,1,1, 23 | 1,0,1,1,1,1,0,0,0,1,1,0,1,1,0,0,0,1,0,0,1,1,1, 24 | 1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,0,1,1,0,1,0,1, 25 | 1,0,0,0,0,0,0,1,1,0,1,0,1,0,1,1,0,0,0,0,1,1,0, 26 | 0,1,1,0,1,0,0,0,1,1,0,1,1,0,1,0,1,1,1,1,0,0,1, 27 | 1,1,0,1,0,0,0,1,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0, 28 | 1,0,0,1,1,0,1,0,1,1,0,0,1,0,0,0,0,1,0,0,0,1,0, 29 | 0,1,1,0,1,0,0,0,1,0,1,0,0,1,0,1,0,1,0,1,0,1,1, 30 | 1,1,0,1,1,1,1,0,1,0,0,0,1,1,0,0,1,1,1,0,1,1,1, 31 | 0,1,0,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,0,0,0,1, 32 | 1,1,0,0,0,1,1,0,1,1,0,1,0,1,1,0,1,0,0,0,1,0,0, 33 | 1,1,0,1,0,1,0,1,1,1,0,1,1,1,1,1,0,1,1,1,0,1,0, 34 | 1,0,0,1,1,0,0,0,0,1,0,0,0,0,0,1,1,1,1,1,1,1,0, 35 | 1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,1,1,0,0,0,0,0, 36 | 0,1,1,0,0,1,0,0,0,0,1,1,1,1,0,0,0,0,1,0,0,1,0, 37 | 0,0,1,1,0,1,1,1,1,0,0,1,1,1,1,1,1,0,1,0,0,1,1, 38 | 1,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,0,0,0,1,0,1,1, 39 | 1,1,1,0,1,1,0,0,1,1,1,0,0,1,1,1,0,1,0,0,0,1,0, 40 | 0,0,0,0,0,0,0,0,0,1,0,1,0,0,1,0,1,0,0,0,1,1,1, 41 | 1,0,0,1,0,0,1,1,1,0,1,1,1,0,0,0,1,1,0,0,0,1,0, 42 | 0,1,1,0,1,0,1,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1, 43 | 1,1,0,1,0,0,1,1,1,1,1,0,0,1,1,0,1,0,0,0,1,1,0, 44 | 1,0,0,1,0,0,0,0,0,0,0,0,1,0,1,1,0,0,0,1,1,0,1, 45 | 1,0,1,1,1,0,1,1,0,0,0,1,0,1,1,0,1,1,0,0,1,0,0, 46 | 1,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,1,0,0,1,0,0,1, 47 | 0,1,0,1,1,1,1,0,0,0,0,1,0,1,1,0,0,1,0,0,0,0,0, 48 | 1,0,1,1,0,1,0,0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,1, 49 | 0,1,1,0,0,1,0,1,1,0,0,0,1,0,1,1,1,0,1,0,1,0,1, 50 | 1,0,1,0,1,0,0,0,0,0,0,1,1,1,0,1,1,0,1,1,0,0,1, 51 | 0,1,0,1,0,0,0,0,1,0,0,0,0,1,1,0,1,0,1,1,0,1,0, 52 | 1,0,0,1,1,1,0,1,1,0,0,1,0,1,0,1,0,0,0,0,1,1,0, 53 | 0,0,1,0,0,0,1,0,1,0,0,0,1,1,1,0,0,0,1,0,1,0,0, 54 | 0,1,1,0,0,1,1,0,0,0,0}; 55 | /*******************************************************************/ 56 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwickert/scikit-dsp-comm/e9fe974157444a905f960df52842ab4b82f65007/tests/__init__.py -------------------------------------------------------------------------------- /tests/sandbox.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from sk_dsp_comm import fec_conv 4 | from sk_dsp_comm import digitalcom as dc 5 | 6 | np.random.seed(100) 7 | 8 | cc = fec_conv.FecConv() 9 | print(cc.Nstates) 10 | 11 | import matplotlib.pyplot as plt 12 | import numpy as np 13 | from sk_dsp_comm import fec_conv as fc 14 | SNRdB = np.arange(2,12,.1) 15 | Pb_uc = fc.conv_Pb_bound(1/2,5,[1,4,12,32,80,192,448,1024],SNRdB,2) 16 | Pb_s = fc.conv_Pb_bound(1/2,5,[1,4,12,32,80,192,448,1024],SNRdB,1) 17 | plt.figure(figsize=(5,5)) 18 | plt.semilogy(SNRdB,Pb_uc) 19 | plt.semilogy(SNRdB,Pb_s) 20 | plt.axis([2,12,1e-7,1e0]) 21 | plt.xlabel(r'$E_b/N_0$ (dB)') 22 | plt.ylabel(r'Symbol Error Probability') 23 | #plt.legend(('Uncoded BPSK','R=1/2, K=5, Soft'),loc='best') 24 | plt.grid(); 25 | plt.show() 26 | -------------------------------------------------------------------------------- /tests/sig_mean_var.h: -------------------------------------------------------------------------------- 1 | //define a FIR coefficient Array 2 | 3 | #include 4 | 5 | #ifndef M_FIR 6 | #define M_FIR 501 7 | #endif 8 | /************************************************************************/ 9 | /* FIR Filter Coefficients */ 10 | float32_t h_FIR[M_FIR] = { 3.000000000000, 3.079006496607, 3.106834405403, 11 | 3.084507527614, 3.013899592989, 2.897698111079, 12 | 2.739354332310, 2.543020186117, 2.313473286152, 13 | 2.056031296574, 1.776457135308, 1.480856646690, 14 | 1.175570504585, 0.867062205440, 0.561804077410, 15 | 0.266163265278,-0.013710349045,-0.271992364112, 16 | -0.503286354810,-0.702718884983,-0.866025403784, 17 | -0.989625549157,-1.070686563582,-1.107173731127, 18 | -1.097886967410,-1.042482931543,-0.941482277400, 19 | -0.796261916344,-0.609032420617,-0.382800951365, 20 | -0.121320343560, 0.170974782483, 0.489043790737, 21 | 0.827326384095, 1.179838066160, 1.540273075918, 22 | 1.902113032590, 2.258739429655, 2.603548050593, 23 | 2.930063345472, 3.232050807569, 3.503625422917, 24 | 3.739354332310, 3.934351943788, 4.084365862308, 25 | 4.185852159907, 4.236038691585, 4.232975366221, 26 | 4.175570504585, 4.063612654151, 3.897777478867, 27 | 3.679619597013, 3.411549497505, 3.096795919964, 28 | 2.739354332310, 2.343922377231, 1.915823381636, 29 | 1.460919227176, 0.985514061843, 0.496250489146, 30 | 0.000000000000,-0.496250489146,-0.985514061843, 31 | -1.460919227176,-1.915823381636,-2.343922377231, 32 | -2.739354332310,-3.096795919964,-3.411549497505, 33 | -3.679619597013,-3.897777478867,-4.063612654151, 34 | -4.175570504585,-4.232975366221,-4.236038691585, 35 | -4.185852159907,-4.084365862308,-3.934351943788, 36 | -3.739354332310,-3.503625422917,-3.232050807569, 37 | -2.930063345472,-2.603548050593,-2.258739429655, 38 | -1.902113032590,-1.540273075918,-1.179838066160, 39 | -0.827326384095,-0.489043790737,-0.170974782483, 40 | 0.121320343560, 0.382800951365, 0.609032420617, 41 | 0.796261916344, 0.941482277400, 1.042482931543, 42 | 1.097886967410, 1.107173731127, 1.070686563582, 43 | 0.989625549157, 0.866025403784, 0.702718884983, 44 | 0.503286354810, 0.271992364112, 0.013710349045, 45 | -0.266163265278,-0.561804077410,-0.867062205440, 46 | -1.175570504585,-1.480856646690,-1.776457135308, 47 | -2.056031296574,-2.313473286152,-2.543020186117, 48 | -2.739354332310,-2.897698111079,-3.013899592989, 49 | -3.084507527614,-3.106834405403,-3.079006496607, 50 | -3.000000000000,-2.869662671636,-2.688720552332, 51 | -2.458769667453,-2.182252829718,-1.862421930669, 52 | -1.503286354810,-1.109548387936,-0.686526713848, 53 | -0.240069297616, 0.223542864692, 0.697699493370, 54 | 1.175570504585, 1.650219358760, 2.114718348025, 55 | 2.562263859468, 2.986289650955, 3.380576209940, 56 | 3.739354332310, 4.057401156765, 4.330127018922, 57 | 4.553651645911, 4.724868394152, 4.841495437116, 58 | 4.902113032590, 4.906186236700, 4.854072680335, 59 | 4.747015278724, 4.587120002090, 4.377319090383, 60 | 4.121320343560, 3.823543356535, 3.489043790737, 61 | 3.123426978286, 2.732752336775, 2.323430229238, 62 | 1.902113032590, 1.475582276334, 1.050633779978, 63 | 0.633962751281, 0.232050807569,-0.148943151135, 64 | -0.503286354810,-0.825768097960,-1.111786560399, 65 | -1.357425035161,-1.559516266149,-1.715693802022, 66 | -1.824429495415,-1.885056514091,-1.897777478867, 67 | -1.863657598055,-1.784602925202,-1.663324121783, 68 | -1.503286354810,-1.308646196821,-1.084176618364, 69 | -0.835181367015,-0.567400208772,-0.286906664174, 70 | -0.000000000000, 0.286906664174, 0.567400208772, 71 | 0.835181367015, 1.084176618364, 1.308646196821, 72 | 1.503286354810, 1.663324121783, 1.784602925202, 73 | 1.863657598055, 1.897777478867, 1.885056514091, 74 | 1.824429495415, 1.715693802022, 1.559516266149, 75 | 1.357425035161, 1.111786560399, 0.825768097960, 76 | 0.503286354810, 0.148943151135,-0.232050807569, 77 | -0.633962751281,-1.050633779978,-1.475582276334, 78 | -1.902113032590,-2.323430229238,-2.732752336775, 79 | -3.123426978286,-3.489043790737,-3.823543356535, 80 | -4.121320343560,-4.377319090383,-4.587120002090, 81 | -4.747015278724,-4.854072680335,-4.906186236700, 82 | -4.902113032590,-4.841495437116,-4.724868394152, 83 | -4.553651645911,-4.330127018922,-4.057401156765, 84 | -3.739354332310,-3.380576209940,-2.986289650955, 85 | -2.562263859468,-2.114718348025,-1.650219358760, 86 | -1.175570504585,-0.697699493370,-0.223542864692, 87 | 0.240069297616, 0.686526713848, 1.109548387936, 88 | 1.503286354810, 1.862421930669, 2.182252829718, 89 | 2.458769667453, 2.688720552332, 2.869662671636, 90 | 3.000000000000, 3.079006496607, 3.106834405403, 91 | 3.084507527614, 3.013899592989, 2.897698111079, 92 | 2.739354332310, 2.543020186117, 2.313473286152, 93 | 2.056031296574, 1.776457135308, 1.480856646690, 94 | 1.175570504585, 0.867062205440, 0.561804077410, 95 | 0.266163265278,-0.013710349045,-0.271992364112, 96 | -0.503286354810,-0.702718884983,-0.866025403784, 97 | -0.989625549157,-1.070686563582,-1.107173731127, 98 | -1.097886967410,-1.042482931543,-0.941482277400, 99 | -0.796261916344,-0.609032420617,-0.382800951365, 100 | -0.121320343560, 0.170974782483, 0.489043790737, 101 | 0.827326384095, 1.179838066160, 1.540273075918, 102 | 1.902113032590, 2.258739429655, 2.603548050593, 103 | 2.930063345472, 3.232050807569, 3.503625422917, 104 | 3.739354332310, 3.934351943788, 4.084365862308, 105 | 4.185852159907, 4.236038691585, 4.232975366221, 106 | 4.175570504585, 4.063612654151, 3.897777478867, 107 | 3.679619597013, 3.411549497505, 3.096795919964, 108 | 2.739354332310, 2.343922377231, 1.915823381636, 109 | 1.460919227176, 0.985514061843, 0.496250489146, 110 | 0.000000000000,-0.496250489146,-0.985514061843, 111 | -1.460919227176,-1.915823381636,-2.343922377231, 112 | -2.739354332310,-3.096795919964,-3.411549497505, 113 | -3.679619597013,-3.897777478867,-4.063612654151, 114 | -4.175570504585,-4.232975366221,-4.236038691585, 115 | -4.185852159907,-4.084365862308,-3.934351943788, 116 | -3.739354332310,-3.503625422917,-3.232050807569, 117 | -2.930063345472,-2.603548050593,-2.258739429655, 118 | -1.902113032590,-1.540273075918,-1.179838066160, 119 | -0.827326384095,-0.489043790737,-0.170974782483, 120 | 0.121320343560, 0.382800951365, 0.609032420617, 121 | 0.796261916344, 0.941482277400, 1.042482931543, 122 | 1.097886967410, 1.107173731127, 1.070686563582, 123 | 0.989625549157, 0.866025403784, 0.702718884983, 124 | 0.503286354810, 0.271992364112, 0.013710349045, 125 | -0.266163265278,-0.561804077410,-0.867062205440, 126 | -1.175570504585,-1.480856646690,-1.776457135308, 127 | -2.056031296574,-2.313473286152,-2.543020186117, 128 | -2.739354332310,-2.897698111079,-3.013899592989, 129 | -3.084507527614,-3.106834405403,-3.079006496607, 130 | -3.000000000000,-2.869662671636,-2.688720552332, 131 | -2.458769667453,-2.182252829718,-1.862421930669, 132 | -1.503286354810,-1.109548387936,-0.686526713848, 133 | -0.240069297616, 0.223542864692, 0.697699493370, 134 | 1.175570504585, 1.650219358760, 2.114718348025, 135 | 2.562263859468, 2.986289650955, 3.380576209940, 136 | 3.739354332310, 4.057401156765, 4.330127018922, 137 | 4.553651645911, 4.724868394152, 4.841495437116, 138 | 4.902113032590, 4.906186236700, 4.854072680335, 139 | 4.747015278724, 4.587120002090, 4.377319090383, 140 | 4.121320343560, 3.823543356535, 3.489043790737, 141 | 3.123426978286, 2.732752336775, 2.323430229238, 142 | 1.902113032590, 1.475582276334, 1.050633779978, 143 | 0.633962751281, 0.232050807569,-0.148943151135, 144 | -0.503286354810,-0.825768097960,-1.111786560399, 145 | -1.357425035161,-1.559516266149,-1.715693802022, 146 | -1.824429495415,-1.885056514091,-1.897777478867, 147 | -1.863657598055,-1.784602925202,-1.663324121783, 148 | -1.503286354810,-1.308646196821,-1.084176618365, 149 | -0.835181367015,-0.567400208772,-0.286906664174, 150 | -0.000000000000, 0.286906664174, 0.567400208772, 151 | 0.835181367015, 1.084176618364, 1.308646196821, 152 | 1.503286354810, 1.663324121783, 1.784602925202, 153 | 1.863657598055, 1.897777478867, 1.885056514091, 154 | 1.824429495415, 1.715693802022, 1.559516266149, 155 | 1.357425035161, 1.111786560399, 0.825768097960, 156 | 0.503286354810, 0.148943151135,-0.232050807569, 157 | -0.633962751281,-1.050633779978,-1.475582276334, 158 | -1.902113032590,-2.323430229238,-2.732752336775, 159 | -3.123426978286,-3.489043790737,-3.823543356535, 160 | -4.121320343560,-4.377319090383,-4.587120002090, 161 | -4.747015278724,-4.854072680335,-4.906186236700, 162 | -4.902113032590,-4.841495437116,-4.724868394152, 163 | -4.553651645911,-4.330127018922,-4.057401156765, 164 | -3.739354332310,-3.380576209940,-2.986289650955, 165 | -2.562263859468,-2.114718348025,-1.650219358760, 166 | -1.175570504585,-0.697699493370,-0.223542864692, 167 | 0.240069297616, 0.686526713848, 1.109548387936, 168 | 1.503286354810, 1.862421930669, 2.182252829718, 169 | 2.458769667453, 2.688720552332, 2.869662671636, 170 | 3.000000000000, 3.079006496607, 3.106834405403, 171 | 3.084507527614, 3.013899592989, 2.897698111079, 172 | 2.739354332310, 2.543020186117, 2.313473286152, 173 | 2.056031296574, 1.776457135308, 1.480856646690, 174 | 1.175570504585, 0.867062205440, 0.561804077410, 175 | 0.266163265278,-0.013710349045,-0.271992364112, 176 | -0.503286354810,-0.702718884983,-0.866025403784}; 177 | /************************************************************************/ 178 | -------------------------------------------------------------------------------- /tests/test_coeff2header.py: -------------------------------------------------------------------------------- 1 | from .test_helper import SKDSPCommTest 2 | 3 | from sk_dsp_comm import coeff2header as c2head 4 | import tempfile 5 | import os 6 | import numpy as np 7 | from logging import getLogger 8 | log = getLogger(__name__) 9 | 10 | dir_path = os.path.dirname(os.path.realpath(__file__)) + '/' 11 | 12 | 13 | class TestCoeff2header(SKDSPCommTest): 14 | 15 | @classmethod 16 | def setUpClass(cls): 17 | cls.tmp_files = [] 18 | 19 | @classmethod 20 | def tearDownClass(cls): 21 | for filename in cls.tmp_files: 22 | try: 23 | os.unlink(filename) 24 | except OSError as ose: 25 | log.error(ose) 26 | log.error("File %s not found" % filename) 27 | 28 | def test_fir_header(self): 29 | """ 30 | Test FIR header. 31 | :return: 32 | """ 33 | f_header_check = open(dir_path + 'sig_mean_var.h', 'r') 34 | f_header_check = f_header_check.readlines() 35 | f1 = 1000 36 | f2 = 400 37 | fs = 48000 38 | n = np.arange(0, 501) 39 | x = 3 * np.cos(2 * np.pi * f1 / fs * n) + 2 * np.sin(2 * np.pi * f2 / fs * n) 40 | test_fir = tempfile.NamedTemporaryFile() 41 | self.tmp_files.append(test_fir.name) 42 | c2head.fir_header(test_fir.name, x) 43 | test_fir_lines = test_fir.readlines() 44 | for line in range(0, len(f_header_check)): 45 | self.assertEqual(f_header_check[line], test_fir_lines[line].decode('UTF-8')) 46 | 47 | def test_ca_1(self): 48 | """ 49 | Test CA header code 1. 50 | :return: 51 | """ 52 | ca_1 = open(dir_path + 'CA_1.h', 'r') 53 | ca_1 = ca_1.readlines() 54 | test_1 = tempfile.NamedTemporaryFile() 55 | self.tmp_files.append(test_1.name) 56 | c2head.ca_code_header(test_1.name, 1) 57 | test_1_lines = test_1.readlines() 58 | for line in range(0, len(ca_1)): 59 | self.assertEqual(ca_1[line], test_1_lines[line].decode('UTF-8')) 60 | 61 | def test_ca_12(self): 62 | """ 63 | Test CA header code 12. 64 | :return: 65 | """ 66 | ca_1 = open(dir_path + 'CA_12.h', 'r') 67 | ca_1 = ca_1.readlines() 68 | test_12 = tempfile.NamedTemporaryFile() 69 | self.tmp_files.append(test_12.name) 70 | c2head.ca_code_header(test_12.name, 12) 71 | test_12_lines = test_12.readlines() 72 | for line in range(0, len(ca_1)): 73 | self.assertEqual(ca_1[line], test_12_lines[line].decode('UTF-8')) 74 | -------------------------------------------------------------------------------- /tests/test_digitalcom.py: -------------------------------------------------------------------------------- 1 | from .test_helper import SKDSPCommTest 2 | 3 | import numpy as np 4 | from sk_dsp_comm import digitalcom as dc 5 | from numpy import testing as npt 6 | from scipy import signal 7 | 8 | 9 | class TestDigitalcom(SKDSPCommTest): 10 | _multiprocess_can_split_ = True 11 | 12 | def test_farrow_example(self): 13 | x = np.arange(0, 10) 14 | self.skipTest("Test not implemented yet.") 15 | 16 | def test_bit_errors_no_errors(self): 17 | bits = 10 18 | transmit = np.zeros(bits) 19 | receive = np.zeros(bits) 20 | bit_count, bit_errors = dc.bit_errors(transmit, receive) 21 | self.assertEqual(bit_count, bits) 22 | self.assertEqual(bit_errors, 0) 23 | 24 | def test_bit_errors_five_errors(self): 25 | """ 26 | Test for 5 bit errors. Uses ones for data alignment. 27 | :return: 28 | """ 29 | bits = 100 30 | transmit = np.ones(bits) 31 | receive = np.ones(bits) 32 | rand_bits = [80, 75, 59, 3, 7] 33 | for rand_bit in rand_bits: 34 | receive[rand_bit] -= 1 35 | bit_count, bit_errors = dc.bit_errors(transmit, receive) 36 | self.assertEqual(bit_count, bits) 37 | self.assertEqual(bit_errors, len(rand_bits)) 38 | 39 | def test_CIC_4(self): 40 | """ 41 | 4 taps, 7 sections 42 | :return: 43 | """ 44 | b_test = np.array([ 6.10351562e-05, 4.27246094e-04, 1.70898438e-03, 45 | 5.12695312e-03, 1.23901367e-02, 2.52075195e-02, 46 | 4.44335938e-02, 6.88476562e-02, 9.48486328e-02, 47 | 1.17065430e-01, 1.29882812e-01, 1.29882812e-01, 48 | 1.17065430e-01, 9.48486328e-02, 6.88476562e-02, 49 | 4.44335938e-02, 2.52075195e-02, 1.23901367e-02, 50 | 5.12695312e-03, 1.70898438e-03, 4.27246094e-04, 51 | 6.10351562e-05]) 52 | b = dc.cic(4, 7) 53 | npt.assert_almost_equal(b_test, b) 54 | 55 | def test_CIC_1(self): 56 | """ 57 | 4 taps, 1 section 58 | :return: 59 | """ 60 | b_test = np.ones(4) / 4 61 | b = dc.cic(4, 1) 62 | npt.assert_almost_equal(b_test, b) 63 | 64 | def test_QAM_bb_qpsk_src(self): 65 | np.random.seed(100) 66 | x_test, b_test, t_test = (np.array([ 0.00585723+0.00585723j, -0.00275016-0.00275016j, 67 | -0.00164540-0.01335987j, 0.00887646+0.01437677j, 68 | -0.01540288+0.01131686j, 0.00480440-0.02394915j, 69 | 0.02505607+0.02585128j, -0.04406383-0.00716616j, 70 | -0.02797722-0.08626139j, 0.11024504+0.1600832j , 71 | 0.00580570+0.16357483j, -0.44629859-0.76924864j, 72 | -0.96387506-1.22739262j, -1.32990076+0.11435352j, 73 | -1.06357060+1.2446656j , 0.04406383+0.22076409j, 74 | 1.06649175-1.18402117j, 1.21485132-1.24832839j, 75 | 0.97347224-0.94165619j, 0.88372072-0.89875699j]), 76 | np.array([-0.00293625, 0.00137866, 0.00376109, -0.00582846, 0.00102417, 77 | 0.00479866, -0.01276 , 0.01284087, 0.02863407, -0.06775815, 78 | -0.04245547, 0.30467864, 0.54924435, 0.30467864, -0.04245547, 79 | -0.06775815, 0.02863407, 0.01284087, -0.01276 , 0.00479866, 80 | 0.00102417, -0.00582846, 0.00376109, 0.00137866, -0.00293625]), 81 | np.array([-1.-1.j, -1.+1.j, 1.-1.j, 1.-1.j, 1.-1.j, 1.-1.j, -1.+1.j, 82 | -1.-1.j, -1.-1.j, -1.+1.j])) 83 | x, b, t = dc.qam_bb(10, 2, mod='qpsk', pulse='src') 84 | npt.assert_almost_equal(x, x_test) 85 | npt.assert_almost_equal(b, b_test) 86 | npt.assert_almost_equal(t, t_test) 87 | 88 | def test_QAM_bb_qpsk_rc(self): 89 | x_test, b_test, t_test = (np.array([ -2.22799382e-18 -2.22799382e-18j, 90 | -4.07129297e-03 -4.07129297e-03j, 91 | 2.22160609e-19 +4.67814826e-18j, 92 | -2.22059175e-03 +5.92199418e-03j, 93 | 6.43926133e-18 -2.91703518e-18j, 94 | 1.97462099e-02 +7.90222150e-03j, 95 | -9.75189466e-18 -1.28297996e-17j, 96 | -4.09888874e-02 -7.30785022e-02j, 97 | 1.05934616e-17 +3.71417032e-17j, 98 | 9.37971907e-02 +2.31071828e-01j, 99 | -1.52549076e-18 -6.78758025e-17j, 100 | -4.10719565e-01 -8.26448725e-01j, 101 | -1.00000000e+00 -1.00000000e+00j, 102 | -1.36231531e+00 +1.25147025e-01j, 103 | -1.00000000e+00 +1.00000000e+00j, 104 | 4.09888874e-02 +2.75737546e-01j, 105 | 1.00000000e+00 -1.00000000e+00j, 106 | 1.24877191e+00 -1.36728049e+00j, 107 | 1.00000000e+00 -1.00000000e+00j, 8.23659721e-01 -7.64661456e-01j]), 108 | np.array([ 1.11223990e-18, 2.03243583e-03, -1.22314501e-18, 109 | -9.23891131e-04, -8.79167710e-19, -6.90120596e-03, 110 | 5.63651951e-18, 2.84718702e-02, -1.19149691e-17, 111 | -8.10891577e-02, 1.73229581e-17, 3.08804252e-01, 112 | 4.99211393e-01, 3.08804252e-01, 1.73229581e-17, 113 | -8.10891577e-02, -1.19149691e-17, 2.84718702e-02, 114 | 5.63651951e-18, -6.90120596e-03, -8.79167710e-19, 115 | -9.23891131e-04, -1.22314501e-18, 2.03243583e-03, 116 | 1.11223990e-18]), np.array([-1.-1.j, -1.+1.j, 1.-1.j, 1.-1.j, 1.-1.j, 1.-1.j, -1.+1.j, 117 | -1.-1.j, -1.-1.j, -1.+1.j])) 118 | x, b, t = dc.qam_bb(10, 2, mod='qpsk', pulse='rc') 119 | npt.assert_almost_equal(x, x_test) 120 | npt.assert_almost_equal(b, b_test) 121 | npt.assert_almost_equal(t, t_test) 122 | 123 | def test_QAM_bb_qpsk_rect(self): 124 | x_test, b_test, t_test = (np.array([-1.-1.j, -1.-1.j, -1.+1.j, -1.+1.j, 1.-1.j, 1.-1.j, 1.-1.j, 125 | 1.-1.j, 1.-1.j, 1.-1.j, 1.-1.j, 1.-1.j, -1.+1.j, -1.+1.j, 126 | -1.-1.j, -1.-1.j, -1.-1.j, -1.-1.j, -1.+1.j, -1.+1.j]), np.array([ 0.5, 0.5]), 127 | np.array([-1.-1.j, -1.+1.j, 1.-1.j, 1.-1.j, 1.-1.j, 1.-1.j, -1.+1.j, 128 | -1.-1.j, -1.-1.j, -1.+1.j])) 129 | x, b, t = dc.qam_bb(10, 2, mod='qpsk', pulse='rect') 130 | npt.assert_almost_equal(x, x_test) 131 | npt.assert_almost_equal(b, b_test) 132 | npt.assert_almost_equal(t, t_test) 133 | 134 | def test_QAM_bb_pulse_error(self): 135 | with self.assertRaisesRegex(ValueError, 'pulse shape must be src, rc, or rect'): 136 | dc.qam_bb(10, 2, pulse='value') 137 | 138 | def test_QAM_bb_16qam_rect(self): 139 | x_test, b_test, t_test = (np.array([-1.00000000+0.33333333j, -1.00000000+0.33333333j, 140 | -1.00000000-0.33333333j, -1.00000000-0.33333333j, 141 | 1.00000000+0.33333333j, 1.00000000+0.33333333j, 142 | 1.00000000+0.33333333j, 1.00000000+0.33333333j, 143 | 1.00000000+0.33333333j, 1.00000000+0.33333333j, 144 | 1.00000000+0.33333333j, 1.00000000+0.33333333j, 145 | -1.00000000-0.33333333j, -1.00000000-0.33333333j, 146 | 0.33333333-1.j , 0.33333333-1.j , 147 | 0.33333333-1.j , 0.33333333-1.j , 148 | -1.00000000+1.j , -1.00000000+1.j ]), np.array([ 0.5, 0.5]), 149 | np.array([-3.+1.j, -3.-1.j, 3.+1.j, 3.+1.j, 3.+1.j, 3.+1.j, -3.-1.j, 150 | 1.-3.j, 1.-3.j, -3.+3.j])) 151 | x, b, t = dc.qam_bb(10, 2, mod='16qam', pulse='rect') 152 | npt.assert_almost_equal(x, x_test) 153 | npt.assert_almost_equal(b, b_test) 154 | npt.assert_almost_equal(t, t_test) 155 | 156 | def test_QAM_bb_64qam_rect(self): 157 | x_test, b_test, t_test = (np.array([-1.00000000-0.42857143j, -1.00000000-0.42857143j, 158 | -1.00000000+0.42857143j, -1.00000000+0.42857143j, 159 | -0.14285714-0.42857143j, -0.14285714-0.42857143j, 160 | 1.00000000-0.42857143j, 1.00000000-0.42857143j, 161 | 1.00000000+0.71428571j, 1.00000000+0.71428571j, 162 | 1.00000000-0.42857143j, 1.00000000-0.42857143j, 163 | -1.00000000-0.71428571j, -1.00000000-0.71428571j, 164 | -0.42857143-1.j , -0.42857143-1.j , 165 | 0.71428571-1.j , 0.71428571-1.j , 166 | 0.14285714+1.j , 0.14285714+1.j ]), np.array([ 0.5, 0.5]), 167 | np.array([-7.-3.j, -7.+3.j, -1.-3.j, 7.-3.j, 7.+5.j, 7.-3.j, -7.-5.j, 168 | -3.-7.j, 5.-7.j, 1.+7.j])) 169 | x, b, t = dc.qam_bb(10, 2, mod='64qam', pulse='rect') 170 | npt.assert_almost_equal(x, x_test) 171 | npt.assert_almost_equal(b, b_test) 172 | npt.assert_almost_equal(t, t_test) 173 | 174 | def test_QAM_bb_256qam_rect(self): 175 | x_test, b_test, t_test = (np.array([ 0.06666667-0.73333333j, 0.06666667-0.73333333j, 176 | 0.06666667-0.33333333j, 0.06666667-0.33333333j, 177 | -0.60000000-0.73333333j, -0.60000000-0.73333333j, 178 | -0.06666667-0.73333333j, -0.06666667-0.73333333j, 179 | -0.06666667+0.86666667j, -0.06666667+0.86666667j, 180 | 1.00000000-0.73333333j, 1.00000000-0.73333333j, 181 | -1.00000000-0.86666667j, -1.00000000-0.86666667j, 182 | 0.33333333-1.j , 0.33333333-1.j , 183 | 0.86666667+0.06666667j, 0.86666667+0.06666667j, 184 | -0.46666667+1.j , -0.46666667+1.j ]), np.array([ 0.5, 0.5]), 185 | np.array([ 1.-11.j, 1. -5.j, -9.-11.j, -1.-11.j, -1.+13.j, 15.-11.j, 186 | -15.-13.j, 5.-15.j, 13. +1.j, -7.+15.j])) 187 | x, b, t = dc.qam_bb(10, 2, mod='256qam', pulse='rect') 188 | npt.assert_almost_equal(x, x_test) 189 | npt.assert_almost_equal(b, b_test) 190 | npt.assert_almost_equal(t, t_test) 191 | 192 | def test_QAM_bb_mod_error(self): 193 | with self.assertRaisesRegex(ValueError, 'Unknown mod_type'): 194 | x, b, t = dc.qam_bb(10, 2, mod='unknown') 195 | 196 | def test_qam_sep_mod_error(self): 197 | tx = np.ones(10) 198 | rx = np.ones(10) 199 | with self.assertRaisesRegex(ValueError, 'Unknown mod_type'): 200 | dc.qam_sep(tx, rx, 'unknown') 201 | 202 | def test_qam_sep_16qam_no_error(self): 203 | Nsymb_test, Nerr_test, SEP_test = (4986, 0, 0.0) 204 | x, b, tx_data = dc.qam_bb(5000, 10, '16qam', 'src') 205 | x = dc.cpx_awgn(x, 20, 10) 206 | y = signal.lfilter(b, 1, x) 207 | Nsymb, Nerr, SEP = dc.qam_sep(tx_data, y[10 + 10 * 12::10], '16qam', n_transient=0) 208 | self.assertEqual(Nsymb, Nsymb_test) 209 | self.assertEqual(Nerr, Nerr_test) 210 | self.assertEqual(SEP, SEP_test) 211 | 212 | def test_qam_sep_16qam_error(self): 213 | Nsymb_test, Nerr_test, SEP_test = (9976, 172, 0.017241379310344827) 214 | x, b, tx_data = dc.qam_bb(10000, 1, '16qam', 'rect') 215 | x = dc.cpx_awgn(x, 15, 1) 216 | y = signal.lfilter(b, 1, x) 217 | Nsymb, Nerr, SEP = dc.qam_sep(tx_data, y[1 * 12::1], '16qam', n_transient=0) 218 | self.assertEqual(Nsymb, Nsymb_test) 219 | self.assertEqual(Nerr, Nerr_test) 220 | self.assertEqual(SEP, SEP_test) 221 | 222 | def test_qam_sep_qpsk(self): 223 | Nsymb_test, Nerr_test, SEP_test = (4986, 0, 0.0) 224 | x,b,tx_data = dc.qam_bb(5000, 10, 'qpsk', 'src') 225 | x = dc.cpx_awgn(x, 20, 10) 226 | y = signal.lfilter(b,1,x) 227 | Nsymb,Nerr,SEP = dc.qam_sep(tx_data, y[10 + 10 * 12::10], 'qpsk', n_transient=0) 228 | self.assertEqual(Nsymb, Nsymb_test) 229 | self.assertEqual(Nerr, Nerr_test) 230 | self.assertEqual(SEP, SEP_test) 231 | 232 | def test_qam_sep_64qam(self): 233 | Nsymb_test, Nerr_test, SEP_test = (4986, 245, 0.04913758523866827) 234 | x, b, tx_data = dc.qam_bb(5000, 10, '64qam', 'src') 235 | x = dc.cpx_awgn(x, 20, 10) 236 | y = signal.lfilter(b, 1, x) 237 | Nsymb, Nerr, SEP = dc.qam_sep(tx_data, y[10 + 10 * 12::10], '64qam', n_transient=0) 238 | self.assertEqual(Nsymb, Nsymb_test) 239 | self.assertEqual(Nerr, Nerr_test) 240 | self.assertEqual(SEP, SEP_test) 241 | 242 | def test_qam_sep_256qam(self): 243 | Nsymb_test, Nerr_test, SEP_test = (4986, 2190, 0.43922984356197353) 244 | x, b, tx_data = dc.qam_bb(5000, 10, '256qam', 'src') 245 | x = dc.cpx_awgn(x, 20, 10) 246 | y = signal.lfilter(b, 1, x) 247 | Nsymb, Nerr, SEP = dc.qam_sep(tx_data, y[10 + 10 * 12::10], '256qam', n_transient=0) 248 | self.assertEqual(Nsymb, Nsymb_test) 249 | self.assertEqual(Nerr, Nerr_test) 250 | self.assertEqual(SEP, SEP_test) 251 | 252 | def test_gmsk_bb(self): 253 | y_test, data_test = (np.array([ 7.07106781e-01 -7.07106781e-01j, 254 | 6.12323400e-17 -1.00000000e+00j, 255 | -7.07106781e-01 -7.07106781e-01j, 256 | -1.00000000e+00 -1.22464680e-16j, 257 | -7.07106781e-01 -7.07106781e-01j, 258 | 6.12323400e-17 -1.00000000e+00j, 259 | 7.07106781e-01 -7.07106781e-01j, 260 | 1.00000000e+00 +0.00000000e+00j, 261 | 7.07106781e-01 +7.07106781e-01j, 262 | 6.12323400e-17 +1.00000000e+00j, 263 | -7.07106781e-01 +7.07106781e-01j, 264 | -1.00000000e+00 +1.22464680e-16j, 265 | -7.07106781e-01 +7.07106781e-01j, 266 | 6.12323400e-17 +1.00000000e+00j, 267 | 7.07106781e-01 +7.07106781e-01j, 268 | 1.00000000e+00 +0.00000000e+00j, 269 | 7.07106781e-01 -7.07106781e-01j, 270 | 6.12323400e-17 -1.00000000e+00j, 271 | -7.07106781e-01 -7.07106781e-01j, 272 | -1.00000000e+00 -1.22464680e-16j]), 273 | np.array([0, 0, 1, 1, 1, 1, 0, 0, 0, 0])) 274 | y, data = dc.gmsk_bb(10, 2) 275 | npt.assert_almost_equal(y, y_test) 276 | npt.assert_equal(data, data_test) 277 | 278 | def test_mpsk_bb_rect(self): 279 | x_test, b_test, data_test = (np.array([ 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j, 280 | 1.+0.j, 1.+0.j, 1.+0.j]), np.array([ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]), 281 | np.array([0, 0, 3, 7, 7, 7, 0, 2, 6, 4])) 282 | x, b, data = dc.mpsk_bb(500, 10, 8, 'rect', 0.35) 283 | npt.assert_equal(x[:10], x_test) 284 | npt.assert_almost_equal(b[:10], b_test) 285 | npt.assert_almost_equal(data[:10], data_test) 286 | 287 | def test_mpsk_bb_rc(self): 288 | x_test, b_test, data_test = (np.array([ 2.22799382e-18+0.j, 1.01671750e-03+0.j, 2.07413572e-03+0.j, 289 | 3.02766347e-03+0.j, 3.73320945e-03+0.j, 4.07129297e-03+0.j, 290 | 3.96939751e-03+0.j, 3.41846688e-03+0.j, 2.48001733e-03+0.j, 291 | 1.28158429e-03+0.j]), np.array([ 2.22799382e-19, 1.01671750e-04, 2.07413572e-04, 292 | 3.02766347e-04, 3.73320945e-04, 4.07129297e-04, 293 | 3.96939751e-04, 3.41846688e-04, 2.48001733e-04, 294 | 1.28158429e-04]), np.array([0, 0, 3, 7, 7, 7, 0, 2, 6, 4])) 295 | x, b, data = dc.mpsk_bb(500, 10, 8, 'rc', 0.35) 296 | npt.assert_almost_equal(x[:10], x_test) 297 | npt.assert_almost_equal(b[:10], b_test) 298 | npt.assert_almost_equal(data[:10], data_test) 299 | 300 | def test_mpsk_bb_src(self): 301 | x_test, b_test, data_test = (np.array([-0.00585723+0.j, -0.00619109+0.j, -0.00534820+0.j, -0.00337561+0.j, 302 | -0.00053042+0.j, 0.00275016+0.j, 0.00591323+0.j, 0.00838014+0.j, 303 | 0.00964778+0.j, 0.00938446+0.j]), np.array([ -5.85723271e-04, -6.19109164e-04, -5.34820232e-04, 304 | -3.37560604e-04, -5.30419514e-05, 2.75015614e-04, 305 | 5.91323287e-04, 8.38013686e-04, 9.64778341e-04, 306 | 9.38445583e-04]), np.array([0, 0, 3, 7, 7, 7, 0, 2, 6, 4])) 307 | x, b, data = dc.mpsk_bb(500, 10, 8, 'src', 0.35) 308 | npt.assert_almost_equal(x[:10], x_test) 309 | npt.assert_almost_equal(b[:10], b_test) 310 | npt.assert_almost_equal(data[:10], data_test) 311 | 312 | def test_mpsk_bb_value_error(self): 313 | with self.assertRaisesRegex(ValueError, "pulse type must be rec, rc, or src"): 314 | x, b, data = dc.mpsk_bb(500, 10, 8, 'error') 315 | 316 | def test_ofdm_tx(self): 317 | x_out_test = np.array([ 0.00000000+0.125j, -0.10185331+0.27369942j, -0.10291586+0.12529202j, 318 | -0.05485981-0.1015143j, -0.02143872-0.09787268j, -0.06906044+0.05231368j, 319 | -0.18815224+0.050888j, -0.26164122-0.15836327j, -0.21940048-0.36048543j, 320 | -0.14486054-0.38169759j, -0.11830476-0.25561157j, -0.07250935-0.12760226j, 321 | 0.05301567-0.08413918j, 0.14316564-0.07020723j, 0.07590886+0.01736066j, 322 | -0.04551924+0.15686941j, -0.03125000+0.21875j, 0.09755018+0.17168517j, 323 | 0.15431728+0.10974492j, 0.08889087+0.04259743j, 0.04284671-0.1107734j, 324 | 0.10071736-0.25986197j, 0.15582045-0.17226253j, 0.06652251+0.12312402j, 325 | -0.15245874+0.29798543j, -0.32346606+0.23845079j, -0.25311017+0.21460293j, 326 | 0.07831717+0.3396657j, 0.43085592+0.30360811j, 0.48116320-0.0505655j, 327 | 0.16656460-0.32765262j, -0.20071609-0.16142259j, -0.25000000+0.1875j, 328 | 0.04290155+0.25900306j, 0.33313987+0.08484705j, 0.28478134-0.00986648j, 329 | -0.05936711+0.00190181j, -0.30195965-0.0628197j, -0.12280721-0.1651266j, 330 | 0.31807654-0.16252886j, 0.53190048-0.13951457j, 0.31342228-0.20065005j, 331 | 0.00806130-0.17969398j, -0.00105255+0.03378639j, 0.15279016+0.16494501j, 332 | 0.09844557-0.009236j, -0.11589986-0.20597693j, -0.10438721-0.09983656j, 333 | 0.15625000+0.09375j, 0.22805837+0.03951473j,]) 334 | x1, b1, IQ_data1 = dc.qam_bb(50000, 1, '16qam') 335 | x_out = dc.ofdm_tx(IQ_data1, 32, 64, 0, True, 0) 336 | npt.assert_almost_equal(x_out[:50], x_out_test) 337 | 338 | def test_ofdm_rx(self): 339 | z_out_test, H_test = (np.array([-3.11740028 - 0.90748269j, -3.11628187 - 0.88948888j, 340 | 2.88565859 + 1.13255112j, 2.89076997 + 3.16052588j, 341 | 2.90396853 + 1.19595053j, 2.93439648 + 1.23703401j, 342 | -3.00724063 - 0.72880083j, 1.07519281 + 1.27075039j, 343 | 1.14472192 + 3.22099905j, -2.82962216 + 1.15148633j, 344 | 1.16245397 + 3.09533441j, -0.85799363 - 0.94063529j, 345 | 1.12036257 + 1.03825793j, 1.10109739 + 1.02622557j, 346 | 1.08488052 - 2.98041713j, 1.07132873 + 1.01625511j, 347 | -0.92119499 + 3.01872286j, -2.91683903 - 0.9906338j, 348 | -2.91213253 - 3.00295552j, 3.09229992 - 3.01974828j]), 349 | np.array([1.42289223 - 1.43696423e-01j, 1.34580486 - 2.66705232e-01j, 350 | 1.23071709 - 3.51736667e-01j, 1.09530096 - 3.87688911e-01j, 351 | 0.95992898 - 3.71339473e-01j, 0.84428862 - 3.07656711e-01j, 352 | 0.76421410 - 2.08710707e-01j, 0.72928932 - 9.14213562e-02j, 353 | 0.74161551 + 2.54124114e-02j, 0.79590656 + 1.24244087e-01j, 354 | 0.88082345 + 1.91510354e-01j, 0.98123573 + 2.19504072e-01j, 355 | 1.08094630 + 2.07118520e-01j, 1.16536231 + 1.59418930e-01j, 356 | 1.22364402 + 8.62177170e-02j, 1.25000000 - 2.77555756e-17j, 357 | 1.25000000 + 2.77555756e-17j, 1.22364402 - 8.62177170e-02j, 358 | 1.16536231 - 1.59418930e-01j, 1.08094630 - 2.07118520e-01j, 359 | 0.98123573 - 2.19504072e-01j, 0.88082345 - 1.91510354e-01j, 360 | 0.79590656 - 1.24244087e-01j, 0.74161551 - 2.54124114e-02j, 361 | 0.72928932 + 9.14213562e-02j, 0.76421410 + 2.08710707e-01j, 362 | 0.84428862 + 3.07656711e-01j, 0.95992898 + 3.71339473e-01j, 363 | 1.09530096 + 3.87688911e-01j, 1.23071709 + 3.51736667e-01j, 364 | 1.34580486 + 2.66705232e-01j, 1.42289223 + 1.43696423e-01j])) 365 | hc = np.array([1.0, 0.1, -0.05, 0.15, 0.2, 0.05]) 366 | x1, b1, IQ_data1 = dc.qam_bb(50000, 1, '16qam') 367 | x_out = dc.ofdm_tx(IQ_data1, 32, 64, 0, True, 0) 368 | c_out = signal.lfilter(hc, 1, x_out) # Apply channel distortion 369 | r_out = dc.cpx_awgn(c_out, 100, 64 / 32) # Es/N0 = 100 dB 370 | z_out, H = dc.ofdm_rx(r_out, 32, 64, -1, True, 0, alpha=0.95, ht=hc); 371 | npt.assert_almost_equal(z_out[:20], z_out_test) 372 | npt.assert_almost_equal(H, H_test) 373 | 374 | def test_ofdm_rx_channel_estimate(self): 375 | z_out_test, H_out_test = (np.array([-2.91356233-0.93854058j, -3.03083561-1.01177886j, 376 | 3.10687062+1.09962706j, 2.91679784+2.79392693j, 377 | 2.95621370+0.87789714j, 2.93521287+1.12869418j, 378 | -3.17675560-1.0834705j , 1.25700626+1.19497994j, 379 | 1.16433902+2.62068101j, -3.10408334+1.08514004j, 380 | 1.02623864+3.01672402j, -0.98366297-1.21602375j, 381 | 0.89577012+1.07687508j, 1.05852406+1.05134363j, 382 | 0.93287609-3.11042385j, 0.99965390+0.88124784j, 383 | -1.16293758+3.08562314j, -2.84891079-1.07199168j, 384 | -3.22236927-2.90425199j, 3.07028549-2.88413491j, 385 | -3.12192058+2.89625467j, 3.18017151-1.09375776j, 386 | -2.78212772+3.05087219j, 1.13471595-2.89218144j, 387 | -3.17092453-1.11298847j, 3.10927184+0.86801524j, 388 | -0.76520964-3.32101721j, -0.94935570+2.86081052j, 389 | 0.93535950+1.10545223j, 1.09394518-1.17966519j, 390 | 3.10748055+1.12377382j, -3.12337017-0.89848715j, 391 | -2.95725651+0.97491592j, 3.14041238-3.01998896j, 392 | -1.05440640+3.04843936j, -0.94130790-0.82179287j, 393 | -0.79049810-1.04083796j, 2.96004080+1.01692442j, 394 | -3.13063510+1.32083138j, -2.58084447-3.28171534j, 395 | 3.09664605+0.82140179j, 2.87565015-1.17002378j, 396 | 2.82351021+2.83242155j, 2.99238994+3.06883778j, 397 | -0.83601519-2.8886988j , 3.05383614+1.22402533j, 398 | -0.92550302+0.92366226j, -0.97707573+3.08608891j, 399 | 0.73489228-2.99163649j, 2.89544691+2.76671634j]), 400 | np.array([ 1.49261307-0.12886832j, 1.36399692-0.24831791j, 401 | 1.24438887-0.41524198j, 1.15276504-0.47480443j, 402 | 1.09981815-0.35438673j, 0.86684483-0.31710329j, 403 | 0.75885865-0.23542562j, 0.76309583-0.19374055j, 404 | 0.61556098+0.09731796j, 0.77281595+0.07096727j, 405 | 0.87593303+0.15642133j, 1.06728467+0.29788462j, 406 | 1.08613086+0.23650714j, 1.12082635+0.09129381j, 407 | 1.31026672+0.17419224j, 1.19459330+0.01027668j, 408 | 1.19745209+0.11471611j, 1.36689249-0.07997548j, 409 | 1.26471663-0.07505238j, 1.14356226-0.19961235j, 410 | 0.84149706-0.21609579j, 0.85489994-0.18101042j, 411 | 0.79502365-0.17155484j, 0.71666634-0.02650505j, 412 | 0.82384118+0.0565963j , 0.74313589+0.28403893j, 413 | 0.88570493+0.29345603j, 0.95203301+0.37888469j, 414 | 0.98676887+0.4108844j , 1.26869289+0.35672436j, 415 | 1.44594176+0.3296819j , 1.48817425+0.07577518j])) 416 | hc = np.array([1.0, 0.1, -0.05, 0.15, 0.2, 0.05]) 417 | x1, b1, IQ_data1 = dc.qam_bb(50000, 1, '16qam') 418 | x_out = dc.ofdm_tx(IQ_data1, 32, 64, 100, True, 10) 419 | c_out = signal.lfilter(hc, 1, x_out) # Apply channel distortion 420 | r_out = dc.cpx_awgn(c_out, 25, 64 / 32) # Es/N0 = 25 dB 421 | z_out, H = dc.ofdm_rx(r_out, 32, 64, 100, True, 10, alpha=0.95, ht=hc) 422 | npt.assert_almost_equal(z_out[:50], z_out_test) 423 | npt.assert_almost_equal(H[:50], H_out_test) 424 | 425 | def test_bin2gray_gray2bin(self): 426 | rand_vals = np.random.randint(0, 15, (10)) 427 | gray_encode = [dc.bin2gray(rv, 4) for rv in rand_vals] 428 | gray_decode = [dc.gray2bin(rv, 4) for rv in gray_encode] 429 | npt.assert_equal(gray_decode, rand_vals) 430 | 431 | def test_BPSK_tx(self): 432 | x, b_test, data0 = dc.bpsk_tx(10, 10, pulse='src') 433 | self.assertEqual(len(data0), 10) 434 | -------------------------------------------------------------------------------- /tests/test_fec_conv.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from sk_dsp_comm import fec_conv 3 | from .test_helper import SKDSPCommTest 4 | import numpy as np 5 | from numpy import testing as npt 6 | from sk_dsp_comm import digitalcom as dc 7 | 8 | 9 | class TestFecConv12(SKDSPCommTest): 10 | _multiprocess_can_split_ = True 11 | 12 | def test_fec_conv_inst(self): 13 | cc1 = fec_conv.FECConv(('101', '111'), Depth=10) # decision depth is 10 14 | 15 | def test_fec_conv_conv_encoder(self): 16 | cc1 = fec_conv.FECConv() 17 | x = np.random.randint(0, 2, 20) 18 | state = '00' 19 | y_test, state_test = (np.array([ 0., 0., 0., 0., 1., 1., 0., 1., 1., 0., 1., 0., 0., 20 | 1., 1., 1., 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 21 | 1., 1., 0., 0., 0., 0., 1., 1., 1., 0., 1., 1., 1., 1.]), '10') 22 | y, state = cc1.conv_encoder(x, state) 23 | npt.assert_almost_equal(y_test, y) 24 | self.assertEqual(state_test, state) 25 | 26 | def test_fec_conv_viterbi_decoder(self): 27 | cc1 = fec_conv.FECConv() 28 | x = np.random.randint(0,2,20) 29 | state = '00' 30 | y, state = cc1.conv_encoder(x, state) 31 | z_test = [ 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0.] 32 | yn = dc.cpx_awgn(2 * y - 1, 5, 1) 33 | yn = (yn.real + 1) / 2 * 7 34 | z = cc1.viterbi_decoder(yn) 35 | npt.assert_almost_equal(z_test, z) 36 | 37 | def test_fec_conv_puncture(self): 38 | yp_test = [0., 0., 0., 1., 0., 1., 1., 0., 0., 1., 1., 0., 0., 0., 0., 1., 1., 0., 39 | 1., 0., 0., 0., 1., 0.] 40 | cc1 = fec_conv.FECConv() 41 | x = np.random.randint(0, 2, 20) 42 | state = '00' 43 | y, state = cc1.conv_encoder(x, state) 44 | yp = cc1.puncture(y, ('110', '101')) 45 | npt.assert_almost_equal(yp_test, yp) 46 | 47 | def test_fec_conv_depuncture(self): 48 | zdpn_test = [-0.18077499, 0.24326595, -0.43694799, 3.5, 3.5, 7.41513671, 49 | -0.55673726, 7.77925472, 7.64176133, 3.5, 3.5, -0.09960601, 50 | -0.50683017, 7.98234306, 6.58202794, 3.5, 3.5, -1.0668518, 51 | 1.54447404, 1.47065852, -0.24028734, 3.5, 3.5, 6.19633424, 52 | 7.1760269, 0.89395647, 7.69735877, 3.5, 3.5, 1.29889556, 53 | -0.31122416, 0.05311373, 7.21216449, 3.5, 3.5, -1.37679829] 54 | cc1 = fec_conv.FECConv() 55 | 56 | x = np.random.randint(0, 2, 20) 57 | state = '00' 58 | y, state = cc1.conv_encoder(x, state) 59 | yp = cc1.puncture(y, ('110', '101')) 60 | ypn = dc.cpx_awgn(2 * yp - 1, 8, 1) 61 | ypn = (ypn.real + 1) / 2 * 7 62 | zdpn = cc1.depuncture(ypn, ('110', '101'), 3.5) # set erase threshold to 7/2 63 | npt.assert_almost_equal(zdpn_test, zdpn) 64 | 65 | def test_fec_conv_Nstates(self): 66 | G = ('111', '101') 67 | cc = fec_conv.FECConv(G) 68 | self.assertEqual(4, cc.Nstates) 69 | 70 | def test_fec_conv_conv_Pb_bound_2(self): 71 | Pb_uc_test = np.array([ 3.75061284e-02, 2.61319950e-02, 1.71725417e-02, 72 | 1.05322374e-02, 5.95386715e-03, 3.05639669e-03, 73 | 1.39980484e-03, 5.60054773e-04, 1.90907774e-04, 74 | 5.38158927e-05, 1.21088933e-05, 2.08499657e-06, 75 | 2.61306795e-07, 2.24575706e-08]) 76 | SNRdB = np.arange(2, 12, .75) 77 | Pb_uc = fec_conv.conv_Pb_bound(1./2., 5, [1, 4, 12, 32, 80, 192, 448, 1024], SNRdB, 2) 78 | npt.assert_almost_equal(Pb_uc_test, Pb_uc) 79 | 80 | def test_fec_conv_Pb_bound_1(self): 81 | Pb_s_test = np.array([ 4.38709821e-02, 1.10836941e-02, 2.49608462e-03, 82 | 5.06172637e-04, 9.14155349e-05, 1.41608827e-05, 83 | 1.77960366e-06, 1.69942208e-07, 1.14537090e-08, 84 | 5.00593591e-10, 1.28592062e-11, 1.73135359e-13, 85 | 1.06932930e-15, 2.59433518e-18]) 86 | SNRdB = np.arange(2, 12, .75) 87 | Pb_s = fec_conv.conv_Pb_bound(1./2.,5,[1,4,12,32,80,192,448,1024],SNRdB,1) 88 | npt.assert_almost_equal(Pb_s_test, Pb_s) -------------------------------------------------------------------------------- /tests/test_helper.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | import numpy as np 3 | 4 | class SKDSPCommTest(TestCase): 5 | 6 | def setUp(self): 7 | np.random.seed(100) -------------------------------------------------------------------------------- /tests/test_imports.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | 4 | class TestImports(TestCase): 5 | _multiprocess_can_split_ = True 6 | 7 | def test_coeff2header_import(self): 8 | import sk_dsp_comm.coeff2header 9 | 10 | def test_coeff2header_from(self): 11 | from sk_dsp_comm import coeff2header 12 | 13 | def test_digitalcom_import(self): 14 | import sk_dsp_comm.digitalcom 15 | 16 | def test_digitalcom_from(self): 17 | from sk_dsp_comm import digitalcom 18 | 19 | def test_fec_conv_import(self): 20 | import sk_dsp_comm.fec_conv 21 | 22 | def test_fec_conv_from(self): 23 | from sk_dsp_comm import digitalcom 24 | 25 | def test_fir_design_helper_import(self): 26 | from sk_dsp_comm import fir_design_helper 27 | 28 | def test_fir_design_helper_from(self): 29 | import sk_dsp_comm.fir_design_helper 30 | 31 | def test_iir_design_helper_from(self): 32 | from sk_dsp_comm import iir_design_helper 33 | 34 | def test_iir_design_helper_import(self): 35 | import sk_dsp_comm.iir_design_helper 36 | 37 | def test_multirate_helper_from(self): 38 | from sk_dsp_comm import multirate_helper 39 | 40 | def test_multirate_helper_import(self): 41 | import sk_dsp_comm.multirate_helper 42 | 43 | def test_sigsys_from(self): 44 | from sk_dsp_comm import sigsys 45 | 46 | def test_sigsys_import(self): 47 | import sk_dsp_comm.sigsys 48 | 49 | def test_synchronization_from(self): 50 | from sk_dsp_comm import synchronization 51 | 52 | def test_synchronization_import(self): 53 | import sk_dsp_comm.synchronization -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py37,py38,py39,py310,docs 3 | [testenv] 4 | deps= 5 | -rrequirements.txt 6 | pytest 7 | commands=pytest ./tests/ 8 | setenv= 9 | PYTHONHASHSEED = 100 10 | passenv= DISPLAY 11 | [testenv:docs] 12 | deps = 13 | -rdocs/requirements.txt 14 | commands = 15 | make -j --directory=docs clean html 16 | whitelist_externals = 17 | make --------------------------------------------------------------------------------