├── .gitignore ├── LICENSE ├── README.md ├── data ├── README.md ├── by-sa.png ├── cc-zero.png ├── general.png ├── publicdomain.png ├── rir_clap.wav ├── sfa │ ├── noise_rec_one.wav │ ├── noise_rec_two.wav │ └── xmas_noise_rec.wav ├── sfs │ ├── sfs_continuous.png │ └── sfs_discrete.png ├── volume.png ├── wooden_boards.jpg └── xmas.wav ├── detailed_packages_list_yml.txt ├── farfield_directivity_ula.ipynb ├── index.ipynb ├── intro-solutions.ipynb ├── intro.ipynb ├── linear_systems_I-solutions.ipynb ├── linear_systems_I.ipynb ├── linear_systems_II-solutions.ipynb ├── linear_systems_II.ipynb ├── loudspeakers.ipynb ├── microphones-solutions.ipynb ├── microphones.ipynb ├── physics_of_sound_I-solutions.ipynb ├── physics_of_sound_I.ipynb ├── physics_of_sound_II-solutions.ipynb ├── physics_of_sound_II.ipynb ├── plot_direction.py ├── requirements.txt ├── sound_field_analysis.ipynb ├── sound_field_synthesis.ipynb └── tools.py /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints/ 2 | __pycache__/ 3 | *.pyc 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Selected Topics in Audio Signal Processing - Exercises 2 | ====================================================== 3 | 4 | - [static web pages (using nbviewer)](http://nbviewer.ipython.org/github/spatialaudio/selected-topics-in-audio-signal-processing-exercises/blob/master/index.ipynb) 5 | - [interactive web based (using mybinder)](https://mybinder.org/v2/gh/spatialaudio/selected-topics-in-audio-signal-processing-exercises/HEAD?filepath=index.ipynb) 6 | 7 | - for local usage create a conda environment (`mystiasp`), clone the git repository 8 | and start jupyter notebook or jupyter lab 9 | - created with `conda 4.9.2` 10 | - currently we rely on some older matplotlib version 11 | - `conda create -n mystiasp python=3.7.8 pip=20.2.4 numpy=1.19.4 scipy=1.5.3 matplotlib=3.1.3 jupyter=1.0.0 notebook=6.1.5 jupyterlab=2.2.9 pydocstyle=5.1.1 pycodestyle=2.6.0 autopep8=1.5.4 flake8=3.8.4 ipykernel=5.3.4 nb_conda=2.2.1 jupyter_nbextensions_configurator=0.4.1 jupyter_contrib_nbextensions=0.5.1` 12 | - `conda activate mystiasp` 13 | - we need to install pip packages since conda does not have them: 14 | - `pip install soundfile==0.10.3.post1` 15 | - `pip install sounddevice==0.4.1` 16 | - `python3 -m pip install sfs==0.5.0 --user` 17 | - make sure that the kernel is accesible for the notebooks: 18 | - `python -m ipykernel install --user --name mystiasp --display-name "mystiasp"` 19 | - now clone the repo: 20 | - `cd git` or whatever is a good root folder 21 | - `git clone https://github.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises.git` 22 | - `cd selected-topics-in-audio-signal-processing-exercises/` 23 | - `jupyter notebook index.ipynb` or `jupyter lab index.ipynb` 24 | - make sure that `mystiasp` kernel is used for the notebooks 25 | - have fun with the playgrounds 26 | 27 | Copyright 28 | --------- 29 | 30 | The authors waive copyright and related rights in the work through the 31 | [CC0 1.0 Universal public domain dedication](http://creativecommons.org/publicdomain/zero/1.0/). 32 | 33 | This is supposed to be an [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources) (OER). 34 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | Some Audio Files for Use in the Exercises 2 | ========================================= 3 | 4 | Filename: [rir_clap.wav](rir_clap.wav) 5 | Description: The room impulse response (RIR) of a seminar room, measured by 6 | clapping two wooden boards together and recording the result with a 7 | microphone. 8 | Recorded with [Audacity][] 9 | [![CC0 1.0 Public Domain Dedication](cc-zero.png)][CC0 1.0] 10 | 11 | Filename: [xmas.wav](xmas.wav) 12 | Description: "Wishing you a Merry Christmas, and a Happy New Year" (male voice) 13 | Source: http://www.freesound.org/people/davidbain/sounds/136777/ 14 | [![CC0 1.0 Public Domain Dedication](cc-zero.png)][CC0 1.0] 15 | 16 | Filenames: `xmas_*.wav` 17 | Description: Modifications of `xmas.wav` (see above). 18 | [![CC0 1.0 Public Domain Dedication](cc-zero.png)][CC0 1.0] 19 | 20 | [Audacity]: http://audacityteam.org/ 21 | [CC0 1.0]: http://creativecommons.org/publicdomain/zero/1.0/ 22 | [CC BY-SA 3.0]: http://creativecommons.org/licenses/by-sa/3.0/ 23 | -------------------------------------------------------------------------------- /data/by-sa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/by-sa.png -------------------------------------------------------------------------------- /data/cc-zero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/cc-zero.png -------------------------------------------------------------------------------- /data/general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/general.png -------------------------------------------------------------------------------- /data/publicdomain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/publicdomain.png -------------------------------------------------------------------------------- /data/rir_clap.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/rir_clap.wav -------------------------------------------------------------------------------- /data/sfa/noise_rec_one.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/sfa/noise_rec_one.wav -------------------------------------------------------------------------------- /data/sfa/noise_rec_two.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/sfa/noise_rec_two.wav -------------------------------------------------------------------------------- /data/sfa/xmas_noise_rec.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/sfa/xmas_noise_rec.wav -------------------------------------------------------------------------------- /data/sfs/sfs_continuous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/sfs/sfs_continuous.png -------------------------------------------------------------------------------- /data/sfs/sfs_discrete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/sfs/sfs_discrete.png -------------------------------------------------------------------------------- /data/volume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/volume.png -------------------------------------------------------------------------------- /data/wooden_boards.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/wooden_boards.jpg -------------------------------------------------------------------------------- /data/xmas.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/ca28a2abb13f0da39949fa2a5e770d2cb662f4b4/data/xmas.wav -------------------------------------------------------------------------------- /detailed_packages_list_yml.txt: -------------------------------------------------------------------------------- 1 | name: mystiasp 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - appnope=0.1.0 7 | - argon2-cffi=20.1.0 8 | - async_generator=1.10 9 | - attrs=20.3.0 10 | - autopep8=1.5.4 11 | - backcall=0.2.0 12 | - backports=1.0 13 | - backports.functools_lru_cache=1.6.1 14 | - bleach=3.2.1 15 | - brotlipy=0.7.0 16 | - ca-certificates=2020.11.8 17 | - certifi=2020.11.8 18 | - cffi=1.14.4 19 | - chardet=3.0.4 20 | - cryptography=3.2.1 21 | - cycler=0.10.0 22 | - dbus=1.13.6 23 | - decorator=4.4.2 24 | - defusedxml=0.6.0 25 | - entrypoints=0.3 26 | - expat=2.2.9 27 | - flake8=3.8.4 28 | - freetype=2.10.4 29 | - gettext=0.19.8.1 30 | - glib=2.66.3 31 | - icu=67.1 32 | - idna=2.10 33 | - importlib-metadata=3.1.0 34 | - importlib_metadata=3.1.0 35 | - ipykernel=5.3.4 36 | - ipython=7.19.0 37 | - ipython_genutils=0.2.0 38 | - ipywidgets=7.5.1 39 | - jedi=0.17.2 40 | - jinja2=2.11.2 41 | - jpeg=9d 42 | - json5=0.9.5 43 | - jsonschema=3.2.0 44 | - jupyter=1.0.0 45 | - jupyter_client=6.1.7 46 | - jupyter_console=6.2.0 47 | - jupyter_contrib_core=0.3.3 48 | - jupyter_contrib_nbextensions=0.5.1 49 | - jupyter_core=4.7.0 50 | - jupyter_highlight_selected_word=0.2.0 51 | - jupyter_latex_envs=1.4.6 52 | - jupyter_nbextensions_configurator=0.4.1 53 | - jupyterlab=2.2.9 54 | - jupyterlab_pygments=0.1.2 55 | - jupyterlab_server=1.2.0 56 | - kiwisolver=1.3.1 57 | - krb5=1.17.2 58 | - libblas=3.9.0 59 | - libcblas=3.9.0 60 | - libclang=10.0.1 61 | - libcxx=11.0.0 62 | - libedit=3.1.20191231 63 | - libffi=3.2.1 64 | - libgfortran=5.0.0 65 | - libgfortran5=9.3.0 66 | - libglib=2.66.3 67 | - libiconv=1.16 68 | - liblapack=3.9.0 69 | - libllvm10=10.0.1 70 | - libopenblas=0.3.12 71 | - libpng=1.6.37 72 | - libpq=12.3 73 | - libsodium=1.0.18 74 | - libxml2=2.9.10 75 | - libxslt=1.1.33 76 | - llvm-openmp=11.0.0 77 | - lxml=4.6.1 78 | - lz4-c=1.9.2 79 | - markupsafe=1.1.1 80 | - matplotlib=3.1.3 81 | - matplotlib-base=3.1.3 82 | - mccabe=0.6.1 83 | - mistune=0.8.4 84 | - mysql-common=8.0.21 85 | - mysql-libs=8.0.21 86 | - nb_conda=2.2.1 87 | - nb_conda_kernels=2.3.0 88 | - nbclient=0.5.1 89 | - nbconvert=6.0.7 90 | - nbformat=5.0.8 91 | - ncurses=6.2 92 | - nest-asyncio=1.4.3 93 | - notebook=6.1.5 94 | - nspr=4.29 95 | - nss=3.47 96 | - numpy=1.19.4 97 | - openssl=1.1.1h 98 | - packaging=20.4 99 | - pandoc=2.11.2 100 | - pandocfilters=1.4.2 101 | - parso=0.7.1 102 | - pcre=8.44 103 | - pexpect=4.8.0 104 | - pickleshare=0.7.5 105 | - pip=20.2.4 106 | - prometheus_client=0.9.0 107 | - prompt-toolkit=3.0.8 108 | - prompt_toolkit=3.0.8 109 | - ptyprocess=0.6.0 110 | - pycodestyle=2.6.0 111 | - pycparser=2.20 112 | - pydocstyle=5.1.1 113 | - pyflakes=2.2.0 114 | - pygments=2.7.2 115 | - pyopenssl=19.1.0 116 | - pyparsing=2.4.7 117 | - pyqt=5.12.3 118 | - pyrsistent=0.17.3 119 | - pysocks=1.7.1 120 | - python=3.7.8 121 | - python-dateutil=2.8.1 122 | - python_abi=3.7 123 | - pyyaml=5.3.1 124 | - pyzmq=20.0.0 125 | - qt=5.12.9 126 | - qtconsole=5.0.1 127 | - qtpy=1.9.0 128 | - readline=8.0 129 | - requests=2.25.0 130 | - scipy=1.5.3 131 | - send2trash=1.5.0 132 | - setuptools=49.6.0 133 | - six=1.15.0 134 | - snowballstemmer=2.0.0 135 | - sqlite=3.33.0 136 | - terminado=0.9.1 137 | - testpath=0.4.4 138 | - tk=8.6.10 139 | - toml=0.10.2 140 | - tornado=6.1 141 | - traitlets=5.0.5 142 | - urllib3=1.25.11 143 | - wcwidth=0.2.5 144 | - webencodings=0.5.1 145 | - wheel=0.35.1 146 | - widgetsnbextension=3.5.1 147 | - xz=5.2.5 148 | - yaml=0.2.5 149 | - zeromq=4.3.3 150 | - zipp=3.4.0 151 | - zlib=1.2.11 152 | - zstd=1.4.5 153 | - pip: 154 | - pyqt5-sip==4.19.18 155 | - pyqtchart==5.12 156 | - pyqtwebengine==5.12.1 157 | - sfs==0.5.0 158 | - sounddevice==0.4.1 159 | - soundfile==0.10.3.post1 160 | -------------------------------------------------------------------------------- /index.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Selected Topics in Audio Signal Processing - Exercises\n", 8 | "\n", 9 | "This is the main page for the exercises accompanying the lecture \"Selected Topics in Audio Signal Processing\" at [Institute of Communications Engineering](http://www.int.uni-rostock.de/)/[Faculty of Computer Science and Electrical Engineering](http://www.ief.uni-rostock.de/)/[University of Rostock](http://www.uni-rostock.de/).\n", 10 | "\n", 11 | "Registered students can access course details via [StudIP](https://studip.uni-rostock.de/dispatch.php/course/overview?cid=d5c4067c02bb446300d90ba54e5eb1fb) and [LSF](https://lsf.uni-rostock.de/qisserver/rds?state=verpublish&status=init&vmfile=no&moduleCall=webInfo&publishConfFile=webInfo&publishSubDir=veranstaltung&veranstaltung.veranstid=83421).\n", 12 | "\n", 13 | "The lectures/exercises are held each winter semester, starting in 2015.\n", 14 | "\n", 15 | "The notebooks and all additional files should be considered as [Open Educational Resources](https://en.wikipedia.org/wiki/Open_educational_resources)." 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "## Table of Exercises\n", 23 | "\n", 24 | "The exercises are split into the following units.\n", 25 | "Most of them build upon knowledge from previous units, so you should do them in order:\n", 26 | "\n", 27 | "1. [Introduction to Python et al., Working with Audio Signals](intro.ipynb)\n", 28 | "2. [The Physics of Sound, Part I](physics_of_sound_I.ipynb)\n", 29 | "3. [The Physics of Sound, Part II](physics_of_sound_II.ipynb)\n", 30 | "4. [Linear Systems, Part I](linear_systems_I.ipynb)\n", 31 | "5. [Linear Systems, Part II](linear_systems_II.ipynb)\n", 32 | "6. [Microphones](microphones.ipynb)\n", 33 | "7. [Loudspeakers](loudspeakers.ipynb)\n", 34 | "8. [Sound Field Analysis](sound_field_analysis.ipynb)\n", 35 | "8. [Farfield Directivity Linear Array](farfield_directivity_ula.ipynb)\n", 36 | "9. [Sound Field Synthesis](sound_field_synthesis.ipynb)\n" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "## Accessing the Exercises\n", 44 | "\n", 45 | "For the Exercises we use [jupyter notebooks](http://jupyter.org/). For accessing the notebooks, there are three alternatives:\n", 46 | "\n", 47 | "### Alternative 1: Static Web Pages\n", 48 | "\n", 49 | "The Jupyter notebooks for each topic are available as [static web pages](http://nbviewer.jupyter.org/github/spatialaudio/selected-topics-in-audio-signal-processing-exercises/). These are static html pages and you can't change them and try things out.\n", 50 | "\n", 51 | "### Alternative 2: Interactive Online Version \n", 52 | "\n", 53 | "If you don't feel like installing anything, but still want to try out the notebooks, you can\n", 54 | "\n", 55 | "[![Binder](http://mybinder.org/badge.svg)](http://mybinder.org/repo/fietew/selected-topics-in-audio-signal-processing-exercises) \n", 56 | "\n", 57 | "and start playing around immediately. Note, however, that your changes will not be preserved. Once you close your browser, everything will be lost!\n", 58 | "\n", 59 | "### Alternative 3: Use the Notebooks on your own Computer\n", 60 | "\n", 61 | "The most flexible alternative, as you can save your changes. However, you need to download and install some stuff in order to get things working on your machine.\n", 62 | "\n", 63 | "The information below might be outdated. Please have a look at readme.md\n", 64 | "\n", 65 | "https://github.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises\n", 66 | "\n", 67 | "for more recent installation guide using terminal (checked for Mac OS)\n", 68 | "\n", 69 | "#### Install Python \n", 70 | "\n", 71 | "Make sure you always use Python 3!\n", 72 | "\n", 73 | "##### ... under Windows\n", 74 | "For Windows, we recommend to download [Anaconda](https://www.continuum.io/downloads), a multi-platform the Python distribution (make sure to choose Python version 3.x).\n", 75 | "\n", 76 | "##### ... under Linux\n", 77 | "You can also use [Anaconda](https://www.continuum.io/downloads). As an alternative, you can use the following commands to install the necessary packages (Debian/Ubuntu/...):\n", 78 | "\n", 79 | " sudo apt-get update\n", 80 | " sudo apt-get install python3 python3-pip python3-numpy python3-scipy python3-matplotlib python3-cffi libsndfile1 libportaudio2\n", 81 | "\n", 82 | "#### Get the Notebooks and Start Jupyter\n", 83 | "\n", 84 | "Download the current [zip file](https://github.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises/archive/master.zip) (frequently updated) and extract it to an accessible directory. Alternatively, you can also download individual notebook files (with the extension `.ipynb`) from [github.com](https://github.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises) and open them the Jupyter. Note, that some exercises make use of additional files (audio files etc.) which you'll then also have to download manually.\n", 85 | "\n", 86 | "##### ... under Windows\n", 87 | "\n", 88 | "Open the *Anaconda Prompt* (search for it the your start menu). Type the following commands (one after another):\n", 89 | "\n", 90 | " cd path/to/extracted/zip/file\n", 91 | " pip install -r requirements.txt\n", 92 | " python -m notebook\n", 93 | "\n", 94 | "First, this installs a few Python libraries that we will use. Afterwards, this will open a new view in your web browser. Click on [intro.ipynb](intro.ipynb) (or any of the other available notebooks) and enjoy!\n", 95 | "\n", 96 | "##### ... under Linux\n", 97 | "\n", 98 | "Use the Python package management system [pip](http://www.pip-installer.org/) to install a few Python libraries that we will use and then start the Jupyter notebook:\n", 99 | "\n", 100 | " cd path/to/extracted/zip/file\n", 101 | " python3 -m pip install -r requirements.txt --user\n", 102 | " python3 -m notebook\n", 103 | " \n", 104 | "This will open a new view in your web browser with a list of notebooks. Click on [intro.ipynb](intro.ipynb) (or any of the other available notebooks) and enjoy!" 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "## Copyright Information\n", 112 | "\n", 113 | "

\n", 114 | " \n", 116 | " \"CC0\"\n", 117 | " \n", 118 | "
\n", 119 | " To the extent possible under law,\n", 120 | " the person who associated CC0\n", 121 | " with this work has waived all copyright and related or neighboring\n", 122 | " rights to this work.\n", 123 | "

" 124 | ] 125 | } 126 | ], 127 | "metadata": { 128 | "kernelspec": { 129 | "display_name": "mystiasp", 130 | "language": "python", 131 | "name": "mystiasp" 132 | }, 133 | "language_info": { 134 | "codemirror_mode": { 135 | "name": "ipython", 136 | "version": 3 137 | }, 138 | "file_extension": ".py", 139 | "mimetype": "text/x-python", 140 | "name": "python", 141 | "nbconvert_exporter": "python", 142 | "pygments_lexer": "ipython3", 143 | "version": "3.7.8" 144 | } 145 | }, 146 | "nbformat": 4, 147 | "nbformat_minor": 1 148 | } 149 | -------------------------------------------------------------------------------- /intro-solutions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "[back to intro](intro.ipynb)" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": { 14 | "execution": { 15 | "iopub.execute_input": "2020-11-26T13:08:21.367253Z", 16 | "iopub.status.busy": "2020-11-26T13:08:21.366615Z", 17 | "iopub.status.idle": "2020-11-26T13:08:21.646657Z", 18 | "shell.execute_reply": "2020-11-26T13:08:21.647063Z" 19 | } 20 | }, 21 | "outputs": [], 22 | "source": [ 23 | "import numpy as np" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 2, 29 | "metadata": { 30 | "execution": { 31 | "iopub.execute_input": "2020-11-26T13:08:21.657225Z", 32 | "iopub.status.busy": "2020-11-26T13:08:21.656639Z", 33 | "iopub.status.idle": "2020-11-26T13:08:21.665053Z", 34 | "shell.execute_reply": "2020-11-26T13:08:21.665539Z" 35 | } 36 | }, 37 | "outputs": [ 38 | { 39 | "data": { 40 | "text/plain": [ 41 | "array([0, 1, 2, 3, 4, 5])" 42 | ] 43 | }, 44 | "execution_count": 1, 45 | "metadata": {}, 46 | "output_type": "execute_result" 47 | } 48 | ], 49 | "source": [ 50 | "np.arange(6)" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 3, 56 | "metadata": { 57 | "execution": { 58 | "iopub.execute_input": "2020-11-26T13:08:21.670059Z", 59 | "iopub.status.busy": "2020-11-26T13:08:21.669463Z", 60 | "iopub.status.idle": "2020-11-26T13:08:21.671947Z", 61 | "shell.execute_reply": "2020-11-26T13:08:21.672450Z" 62 | } 63 | }, 64 | "outputs": [ 65 | { 66 | "data": { 67 | "text/plain": [ 68 | "(array([0. , 0.1, 0.2, 0.3, 0.4, 0.5]), array([0. , 0.1, 0.2, 0.3, 0.4, 0.5]))" 69 | ] 70 | }, 71 | "execution_count": 1, 72 | "metadata": {}, 73 | "output_type": "execute_result" 74 | } 75 | ], 76 | "source": [ 77 | "np.arange(0, 0.6, 0.1), np.arange(6) * 0.1 # two possibilities" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 4, 83 | "metadata": { 84 | "execution": { 85 | "iopub.execute_input": "2020-11-26T13:08:21.676131Z", 86 | "iopub.status.busy": "2020-11-26T13:08:21.675543Z", 87 | "iopub.status.idle": "2020-11-26T13:08:21.678005Z", 88 | "shell.execute_reply": "2020-11-26T13:08:21.678391Z" 89 | } 90 | }, 91 | "outputs": [ 92 | { 93 | "data": { 94 | "text/plain": [ 95 | "(array([0.5, 0.6, 0.7, 0.8, 0.9, 1. , 1.1]), '<-- wrong result!')" 96 | ] 97 | }, 98 | "execution_count": 1, 99 | "metadata": {}, 100 | "output_type": "execute_result" 101 | } 102 | ], 103 | "source": [ 104 | "np.arange(0.5, 1.1, 0.1), \"<-- wrong result!\"" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 5, 110 | "metadata": { 111 | "execution": { 112 | "iopub.execute_input": "2020-11-26T13:08:21.682129Z", 113 | "iopub.status.busy": "2020-11-26T13:08:21.681450Z", 114 | "iopub.status.idle": "2020-11-26T13:08:21.683883Z", 115 | "shell.execute_reply": "2020-11-26T13:08:21.684285Z" 116 | } 117 | }, 118 | "outputs": [ 119 | { 120 | "data": { 121 | "text/plain": [ 122 | "(array([0.5, 0.6, 0.7, 0.8, 0.9, 1. ]), \"<-- that's right!\")" 123 | ] 124 | }, 125 | "execution_count": 1, 126 | "metadata": {}, 127 | "output_type": "execute_result" 128 | } 129 | ], 130 | "source": [ 131 | "np.arange(5, 11) * 0.1, \"<-- that's right!\"" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 6, 137 | "metadata": { 138 | "execution": { 139 | "iopub.execute_input": "2020-11-26T13:08:21.687917Z", 140 | "iopub.status.busy": "2020-11-26T13:08:21.687360Z", 141 | "iopub.status.idle": "2020-11-26T13:08:21.689883Z", 142 | "shell.execute_reply": "2020-11-26T13:08:21.690362Z" 143 | } 144 | }, 145 | "outputs": [ 146 | { 147 | "data": { 148 | "text/plain": [ 149 | "array([0., 1., 2., 3., 4., 5., 6.])" 150 | ] 151 | }, 152 | "execution_count": 1, 153 | "metadata": {}, 154 | "output_type": "execute_result" 155 | } 156 | ], 157 | "source": [ 158 | "np.linspace(0, 6, 7)" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": 7, 164 | "metadata": { 165 | "execution": { 166 | "iopub.execute_input": "2020-11-26T13:08:21.694598Z", 167 | "iopub.status.busy": "2020-11-26T13:08:21.694051Z", 168 | "iopub.status.idle": "2020-11-26T13:08:21.696437Z", 169 | "shell.execute_reply": "2020-11-26T13:08:21.696855Z" 170 | } 171 | }, 172 | "outputs": [ 173 | { 174 | "data": { 175 | "text/plain": [ 176 | "(array([0., 1., 2., 3., 4., 5.]), array([0., 1., 2., 3., 4., 5.]))" 177 | ] 178 | }, 179 | "execution_count": 1, 180 | "metadata": {}, 181 | "output_type": "execute_result" 182 | } 183 | ], 184 | "source": [ 185 | "np.linspace(0, 6, 6, endpoint=False), np.linspace(0, 5, 6) # two possibilities" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 8, 191 | "metadata": { 192 | "execution": { 193 | "iopub.execute_input": "2020-11-26T13:08:21.700897Z", 194 | "iopub.status.busy": "2020-11-26T13:08:21.700376Z", 195 | "iopub.status.idle": "2020-11-26T13:08:21.702893Z", 196 | "shell.execute_reply": "2020-11-26T13:08:21.703378Z" 197 | } 198 | }, 199 | "outputs": [ 200 | { 201 | "data": { 202 | "text/plain": [ 203 | "(array([0. , 0.1, 0.2, 0.3, 0.4, 0.5]), array([0. , 0.1, 0.2, 0.3, 0.4, 0.5]))" 204 | ] 205 | }, 206 | "execution_count": 1, 207 | "metadata": {}, 208 | "output_type": "execute_result" 209 | } 210 | ], 211 | "source": [ 212 | "np.linspace(0, 0.6, 6, endpoint=False), np.linspace(0, 0.5, 6) # again two possibilities" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": 9, 218 | "metadata": { 219 | "execution": { 220 | "iopub.execute_input": "2020-11-26T13:08:21.707715Z", 221 | "iopub.status.busy": "2020-11-26T13:08:21.707083Z", 222 | "iopub.status.idle": "2020-11-26T13:08:21.709553Z", 223 | "shell.execute_reply": "2020-11-26T13:08:21.709978Z" 224 | } 225 | }, 226 | "outputs": [ 227 | { 228 | "data": { 229 | "text/plain": [ 230 | "(array([0.5, 0.6, 0.7, 0.8, 0.9, 1. ]), array([0.5, 0.6, 0.7, 0.8, 0.9, 1. ]))" 231 | ] 232 | }, 233 | "execution_count": 1, 234 | "metadata": {}, 235 | "output_type": "execute_result" 236 | } 237 | ], 238 | "source": [ 239 | "np.linspace(0.5, 1.1, 6, endpoint=False), np.linspace(0.5, 1, 6) # and again ..." 240 | ] 241 | }, 242 | { 243 | "cell_type": "markdown", 244 | "metadata": {}, 245 | "source": [ 246 | "If the number of elements is known and the step size should be obtained automatically $\\Rightarrow$ `np.linspace()` \n", 247 | "If the step size is known an if it's an integer and the number of elements should be obtained automatically $\\Rightarrow$ `np.arange()`\n", 248 | "\n", 249 | "If the step size is not an integer:\n", 250 | "\n", 251 | "* If the step size is a fraction of integers, you can use `np.arange()` with integers and divide the result accordingly.\n", 252 | "\n", 253 | "* If that's not feasible, calculate the expected number of elements beforehand and use `np.linspace()`" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": 10, 259 | "metadata": { 260 | "execution": { 261 | "iopub.execute_input": "2020-11-26T13:08:21.714178Z", 262 | "iopub.status.busy": "2020-11-26T13:08:21.713610Z", 263 | "iopub.status.idle": "2020-11-26T13:08:21.717376Z", 264 | "shell.execute_reply": "2020-11-26T13:08:21.717860Z" 265 | } 266 | }, 267 | "outputs": [], 268 | "source": [ 269 | "dur, amp, freq, fs = 1, 0.3, 500, 44100\n", 270 | "t = np.arange(np.ceil(dur * fs)) / fs\n", 271 | "y = amp * np.sin(2 * np.pi * freq * t)" 272 | ] 273 | }, 274 | { 275 | "cell_type": "markdown", 276 | "metadata": {}, 277 | "source": [ 278 | "alternative (but inferior) methods to get $t$:" 279 | ] 280 | }, 281 | { 282 | "cell_type": "code", 283 | "execution_count": 11, 284 | "metadata": { 285 | "execution": { 286 | "iopub.execute_input": "2020-11-26T13:08:21.721871Z", 287 | "iopub.status.busy": "2020-11-26T13:08:21.721329Z", 288 | "iopub.status.idle": "2020-11-26T13:08:21.723807Z", 289 | "shell.execute_reply": "2020-11-26T13:08:21.724274Z" 290 | } 291 | }, 292 | "outputs": [], 293 | "source": [ 294 | "t1 = np.arange(0, dur, 1/fs) # implicit rounding of dur!\n", 295 | "t2 = np.arange(0, np.round(dur), 1/fs) # still problematic: arange with floats\n", 296 | "# wrong if dur isn't an integer multiple of 1/fs:\n", 297 | "t3 = np.linspace(0, dur, np.round(dur * fs), endpoint=False)" 298 | ] 299 | }, 300 | { 301 | "cell_type": "markdown", 302 | "metadata": {}, 303 | "source": [ 304 | "Length of `y` must be *exactly* 44100 (using a half-open interval for $t$), not 44101 (which would be longer than 1 second).\n", 305 | "\n", 306 | "Plotting: 2 ways to zoom (there are probably more): draw a rectangle, drag with the right mouse button in pan/zoom mode.\n", 307 | "\n", 308 | "Clicks? Because of discontinuities (also in the derivatives) $\\Rightarrow$ Fade in/out! See [tools.fade()](tools.py)." 309 | ] 310 | }, 311 | { 312 | "cell_type": "code", 313 | "execution_count": 12, 314 | "metadata": { 315 | "execution": { 316 | "iopub.execute_input": "2020-11-26T13:08:21.727905Z", 317 | "iopub.status.busy": "2020-11-26T13:08:21.727310Z", 318 | "iopub.status.idle": "2020-11-26T13:08:22.803546Z", 319 | "shell.execute_reply": "2020-11-26T13:08:22.803108Z" 320 | } 321 | }, 322 | "outputs": [], 323 | "source": [ 324 | "import sounddevice as sd\n", 325 | "import tools\n", 326 | "\n", 327 | "def myplay(data):\n", 328 | " \"\"\"Apply fade in/out and play with 44.1 kHz.\"\"\"\n", 329 | " data = tools.fade(data, 2000, 5000)\n", 330 | " sd.play(data, 44100)" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": 13, 336 | "metadata": { 337 | "execution": { 338 | "iopub.execute_input": "2020-11-26T13:08:22.806314Z", 339 | "iopub.status.busy": "2020-11-26T13:08:22.805689Z", 340 | "iopub.status.idle": "2020-11-26T13:08:22.836772Z", 341 | "shell.execute_reply": "2020-11-26T13:08:22.836245Z" 342 | } 343 | }, 344 | "outputs": [], 345 | "source": [ 346 | "myplay(y)" 347 | ] 348 | }, 349 | { 350 | "cell_type": "code", 351 | "execution_count": 14, 352 | "metadata": { 353 | "execution": { 354 | "iopub.execute_input": "2020-11-26T13:08:22.840208Z", 355 | "iopub.status.busy": "2020-11-26T13:08:22.839763Z", 356 | "iopub.status.idle": "2020-11-26T13:08:22.841440Z", 357 | "shell.execute_reply": "2020-11-26T13:08:22.841868Z" 358 | } 359 | }, 360 | "outputs": [], 361 | "source": [ 362 | "def mysine(frequency, amplitude, duration):\n", 363 | " \"\"\"Generate sine tone with the given parameters @ 44.1 kHz.\"\"\"\n", 364 | " samplerate = 44100\n", 365 | " times = np.arange(np.ceil(duration * samplerate)) / samplerate\n", 366 | " return amplitude * np.sin(2 * np.pi * frequency * times)" 367 | ] 368 | }, 369 | { 370 | "cell_type": "code", 371 | "execution_count": 15, 372 | "metadata": { 373 | "execution": { 374 | "iopub.execute_input": "2020-11-26T13:08:22.844689Z", 375 | "iopub.status.busy": "2020-11-26T13:08:22.844161Z", 376 | "iopub.status.idle": "2020-11-26T13:08:22.851247Z", 377 | "shell.execute_reply": "2020-11-26T13:08:22.850280Z" 378 | } 379 | }, 380 | "outputs": [], 381 | "source": [ 382 | "z = mysine(440, 0.4, 3)" 383 | ] 384 | }, 385 | { 386 | "cell_type": "code", 387 | "execution_count": 16, 388 | "metadata": { 389 | "execution": { 390 | "iopub.execute_input": "2020-11-26T13:08:22.855419Z", 391 | "iopub.status.busy": "2020-11-26T13:08:22.854783Z", 392 | "iopub.status.idle": "2020-11-26T13:08:23.067815Z", 393 | "shell.execute_reply": "2020-11-26T13:08:23.067378Z" 394 | } 395 | }, 396 | "outputs": [], 397 | "source": [ 398 | "myplay(z)" 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": 17, 404 | "metadata": { 405 | "execution": { 406 | "iopub.execute_input": "2020-11-26T13:08:23.071648Z", 407 | "iopub.status.busy": "2020-11-26T13:08:23.071253Z", 408 | "iopub.status.idle": "2020-11-26T13:08:23.765922Z", 409 | "shell.execute_reply": "2020-11-26T13:08:23.766329Z" 410 | } 411 | }, 412 | "outputs": [ 413 | { 414 | "name": "stdout", 415 | "output_type": "stream", 416 | "text": [ 417 | "Using matplotlib backend: MacOSX\n" 418 | ] 419 | } 420 | ], 421 | "source": [ 422 | "%matplotlib\n", 423 | "import matplotlib.pyplot as plt\n", 424 | "\n", 425 | "def myplot(data):\n", 426 | " \"\"\"Create a simple plot @ 44.1 kHz.\"\"\"\n", 427 | " samplerate = 44100\n", 428 | " times = np.arange(len(data)) / samplerate\n", 429 | " plt.plot(times, data)\n", 430 | " plt.xlabel(\"Time / Seconds\")" 431 | ] 432 | }, 433 | { 434 | "cell_type": "code", 435 | "execution_count": 18, 436 | "metadata": { 437 | "execution": { 438 | "iopub.execute_input": "2020-11-26T13:08:23.769397Z", 439 | "iopub.status.busy": "2020-11-26T13:08:23.768987Z", 440 | "iopub.status.idle": "2020-11-26T13:08:23.956377Z", 441 | "shell.execute_reply": "2020-11-26T13:08:23.956790Z" 442 | } 443 | }, 444 | "outputs": [], 445 | "source": [ 446 | "myplot(mysine(440, 0.4, 3))" 447 | ] 448 | }, 449 | { 450 | "cell_type": "code", 451 | "execution_count": 19, 452 | "metadata": { 453 | "execution": { 454 | "iopub.execute_input": "2020-11-26T13:08:23.960687Z", 455 | "iopub.status.busy": "2020-11-26T13:08:23.960269Z", 456 | "iopub.status.idle": "2020-11-26T13:08:23.982524Z", 457 | "shell.execute_reply": "2020-11-26T13:08:23.982918Z" 458 | } 459 | }, 460 | "outputs": [], 461 | "source": [ 462 | "import soundfile as sf\n", 463 | "\n", 464 | "dur, amp = 1, 0.3\n", 465 | "frequencies = 400, 500, 600 # Hz\n", 466 | "fadetime = 2000 # samples\n", 467 | "\n", 468 | "for freq in frequencies:\n", 469 | " sig = mysine(freq, amp, dur)\n", 470 | " sig = tools.fade(sig, fadetime)\n", 471 | " sf.write(\"sine_{}hz.wav\".format(freq), sig, 44100)" 472 | ] 473 | }, 474 | { 475 | "cell_type": "code", 476 | "execution_count": 20, 477 | "metadata": { 478 | "execution": { 479 | "iopub.execute_input": "2020-11-26T13:08:23.987127Z", 480 | "iopub.status.busy": "2020-11-26T13:08:23.986639Z", 481 | "iopub.status.idle": "2020-11-26T13:08:23.997781Z", 482 | "shell.execute_reply": "2020-11-26T13:08:23.998178Z" 483 | } 484 | }, 485 | "outputs": [], 486 | "source": [ 487 | "from scipy import signal\n", 488 | "\n", 489 | "f0, f1 = 100, 5000 # Hz\n", 490 | "amp = 0.2\n", 491 | "dur = 2 # seconds\n", 492 | "fadetime = 2000 # samples\n", 493 | "fs = 44100\n", 494 | "\n", 495 | "t = np.arange(np.ceil(dur * fs)) / fs\n", 496 | "\n", 497 | "for method in 'linear', 'log':\n", 498 | " sweep = amp * signal.chirp(t, f0, dur, f1, method)\n", 499 | " sweep = tools.fade(sweep, fadetime)\n", 500 | " sf.write('sweep_{}.wav'.format(method), sweep, fs)" 501 | ] 502 | }, 503 | { 504 | "cell_type": "code", 505 | "execution_count": 21, 506 | "metadata": { 507 | "execution": { 508 | "iopub.execute_input": "2020-11-26T13:08:24.001635Z", 509 | "iopub.status.busy": "2020-11-26T13:08:24.001235Z", 510 | "iopub.status.idle": "2020-11-26T13:08:24.008584Z", 511 | "shell.execute_reply": "2020-11-26T13:08:24.008162Z" 512 | } 513 | }, 514 | "outputs": [], 515 | "source": [ 516 | "sinetone = mysine(frequency=500, amplitude=0.3, duration=1.5)\n", 517 | "noise = np.random.normal(scale=0.1, size=len(sinetone))\n", 518 | "sine_plus_noise = sinetone + noise" 519 | ] 520 | }, 521 | { 522 | "cell_type": "code", 523 | "execution_count": 22, 524 | "metadata": { 525 | "execution": { 526 | "iopub.execute_input": "2020-11-26T13:08:24.011715Z", 527 | "iopub.status.busy": "2020-11-26T13:08:24.010821Z", 528 | "iopub.status.idle": "2020-11-26T13:08:24.143437Z", 529 | "shell.execute_reply": "2020-11-26T13:08:24.143875Z" 530 | } 531 | }, 532 | "outputs": [], 533 | "source": [ 534 | "myplay(sine_plus_noise)" 535 | ] 536 | }, 537 | { 538 | "cell_type": "code", 539 | "execution_count": 23, 540 | "metadata": { 541 | "execution": { 542 | "iopub.execute_input": "2020-11-26T13:08:24.146837Z", 543 | "iopub.status.busy": "2020-11-26T13:08:24.146366Z", 544 | "iopub.status.idle": "2020-11-26T13:08:24.152536Z", 545 | "shell.execute_reply": "2020-11-26T13:08:24.152079Z" 546 | } 547 | }, 548 | "outputs": [], 549 | "source": [ 550 | "myplot(sine_plus_noise)" 551 | ] 552 | }, 553 | { 554 | "cell_type": "code", 555 | "execution_count": 24, 556 | "metadata": { 557 | "execution": { 558 | "iopub.execute_input": "2020-11-26T13:08:24.155700Z", 559 | "iopub.status.busy": "2020-11-26T13:08:24.155237Z", 560 | "iopub.status.idle": "2020-11-26T13:08:24.161051Z", 561 | "shell.execute_reply": "2020-11-26T13:08:24.160585Z" 562 | } 563 | }, 564 | "outputs": [], 565 | "source": [ 566 | "dur = 2\n", 567 | "amp = 0.2\n", 568 | "\n", 569 | "two_sines = mysine(500, amp, dur) + mysine(507, amp, dur)" 570 | ] 571 | }, 572 | { 573 | "cell_type": "code", 574 | "execution_count": 25, 575 | "metadata": { 576 | "execution": { 577 | "iopub.execute_input": "2020-11-26T13:08:24.164145Z", 578 | "iopub.status.busy": "2020-11-26T13:08:24.163740Z", 579 | "iopub.status.idle": "2020-11-26T13:08:24.296020Z", 580 | "shell.execute_reply": "2020-11-26T13:08:24.296436Z" 581 | } 582 | }, 583 | "outputs": [], 584 | "source": [ 585 | "myplay(two_sines)" 586 | ] 587 | }, 588 | { 589 | "cell_type": "code", 590 | "execution_count": 26, 591 | "metadata": { 592 | "execution": { 593 | "iopub.execute_input": "2020-11-26T13:08:24.300038Z", 594 | "iopub.status.busy": "2020-11-26T13:08:24.299402Z", 595 | "iopub.status.idle": "2020-11-26T13:08:24.306560Z", 596 | "shell.execute_reply": "2020-11-26T13:08:24.306010Z" 597 | } 598 | }, 599 | "outputs": [], 600 | "source": [ 601 | "myplot(two_sines)" 602 | ] 603 | }, 604 | { 605 | "cell_type": "markdown", 606 | "metadata": {}, 607 | "source": [ 608 | "Two sine tones with similar frequencies create \"beats\", see .\n", 609 | "The sum of these two tones is equivalent to an amplitude modulation with a carrier frequency of $\\frac{f_1+f_2}{2}$ and a modulation frequency of $\\frac{f_1-f_2}{2}$.\n", 610 | "\n", 611 | "$$\\cos(2\\pi f_1t)+\\cos(2\\pi f_2t) = 2\\cos\\left(2\\pi\\frac{f_1+f_2}{2}t\\right)\\cos\\left(2\\pi\\frac{f_1-f_2}{2}t\\right)$$\n", 612 | "\n", 613 | "We don't really *hear* the modulation frequency itself, we only hear the envelope of the modulation, therefore the *perceived* beat frequency is $f_{\\text{beat}} = f_1-f_2$." 614 | ] 615 | }, 616 | { 617 | "cell_type": "code", 618 | "execution_count": 27, 619 | "metadata": { 620 | "execution": { 621 | "iopub.execute_input": "2020-11-26T13:08:24.310222Z", 622 | "iopub.status.busy": "2020-11-26T13:08:24.309651Z", 623 | "iopub.status.idle": "2020-11-26T13:08:24.317385Z", 624 | "shell.execute_reply": "2020-11-26T13:08:24.316794Z" 625 | } 626 | }, 627 | "outputs": [], 628 | "source": [ 629 | "stereo_sines = np.column_stack([mysine(400, amp, dur), mysine(600, amp, dur)])" 630 | ] 631 | }, 632 | { 633 | "cell_type": "code", 634 | "execution_count": 28, 635 | "metadata": { 636 | "execution": { 637 | "iopub.execute_input": "2020-11-26T13:08:24.320633Z", 638 | "iopub.status.busy": "2020-11-26T13:08:24.320040Z", 639 | "iopub.status.idle": "2020-11-26T13:08:24.454081Z", 640 | "shell.execute_reply": "2020-11-26T13:08:24.454563Z" 641 | } 642 | }, 643 | "outputs": [], 644 | "source": [ 645 | "myplay(stereo_sines)" 646 | ] 647 | }, 648 | { 649 | "cell_type": "markdown", 650 | "metadata": {}, 651 | "source": [ 652 | "The first column should be the left channel!" 653 | ] 654 | }, 655 | { 656 | "cell_type": "code", 657 | "execution_count": 29, 658 | "metadata": { 659 | "execution": { 660 | "iopub.execute_input": "2020-11-26T13:08:24.459030Z", 661 | "iopub.status.busy": "2020-11-26T13:08:24.458457Z", 662 | "iopub.status.idle": "2020-11-26T13:08:24.462828Z", 663 | "shell.execute_reply": "2020-11-26T13:08:24.462293Z" 664 | } 665 | }, 666 | "outputs": [], 667 | "source": [ 668 | "dur, amp = 1, 0.3\n", 669 | "freq = 500 # Hz\n", 670 | "delay = 0.5 # ms\n", 671 | "fs = 44100\n", 672 | "\n", 673 | "t = np.arange(np.ceil(dur * fs)) / fs\n", 674 | "times = np.column_stack((t, t - delay/1000))\n", 675 | "sig = amp * np.sin(2 * np.pi * freq * times)" 676 | ] 677 | }, 678 | { 679 | "cell_type": "code", 680 | "execution_count": 30, 681 | "metadata": { 682 | "execution": { 683 | "iopub.execute_input": "2020-11-26T13:08:24.466734Z", 684 | "iopub.status.busy": "2020-11-26T13:08:24.466207Z", 685 | "iopub.status.idle": "2020-11-26T13:08:24.599331Z", 686 | "shell.execute_reply": "2020-11-26T13:08:24.598858Z" 687 | } 688 | }, 689 | "outputs": [], 690 | "source": [ 691 | "myplay(sig)" 692 | ] 693 | }, 694 | { 695 | "cell_type": "code", 696 | "execution_count": 31, 697 | "metadata": { 698 | "execution": { 699 | "iopub.execute_input": "2020-11-26T13:08:24.603967Z", 700 | "iopub.status.busy": "2020-11-26T13:08:24.603379Z", 701 | "iopub.status.idle": "2020-11-26T13:08:38.179691Z", 702 | "shell.execute_reply": "2020-11-26T13:08:38.179251Z" 703 | } 704 | }, 705 | "outputs": [], 706 | "source": [ 707 | "dur, amp = 0.5, 0.3\n", 708 | "frequencies = 500, 1000, 2000 # Hz\n", 709 | "delays = 0.6, 0.4, 0.2, 0, -0.2, -0.4, -0.6 # ms\n", 710 | "fs = 44100\n", 711 | "\n", 712 | "t = np.arange(np.ceil(dur * fs)) / fs\n", 713 | "\n", 714 | "for f in frequencies:\n", 715 | " for delay in delays:\n", 716 | " times = np.column_stack((t, t - delay/1000))\n", 717 | " sig = amp * np.sin(2 * np.pi * f * times)\n", 718 | " myplay(sig)\n", 719 | " sd.wait()" 720 | ] 721 | }, 722 | { 723 | "cell_type": "markdown", 724 | "metadata": {}, 725 | "source": [ 726 | "This is supposed to illustrate [Lord Rayleigh's Duplex Theory](http://en.wikipedia.org/wiki/Interaural_time_difference#Duplex_theory) (at least the part about time differences)." 727 | ] 728 | }, 729 | { 730 | "cell_type": "code", 731 | "execution_count": 32, 732 | "metadata": { 733 | "execution": { 734 | "iopub.execute_input": "2020-11-26T13:08:38.861153Z", 735 | "iopub.status.busy": "2020-11-26T13:08:38.860604Z", 736 | "iopub.status.idle": "2020-11-26T13:08:38.864081Z", 737 | "shell.execute_reply": "2020-11-26T13:08:38.863669Z" 738 | } 739 | }, 740 | "outputs": [ 741 | { 742 | "data": { 743 | "text/plain": [ 744 | "array([[0.00000000e+00],\n", 745 | " [2.26757370e-05],\n", 746 | " [4.53514739e-05],\n", 747 | " ...,\n", 748 | " [1.99993197e+00],\n", 749 | " [1.99995465e+00],\n", 750 | " [1.99997732e+00]])" 751 | ] 752 | }, 753 | "execution_count": 1, 754 | "metadata": {}, 755 | "output_type": "execute_result" 756 | } 757 | ], 758 | "source": [ 759 | "dur, amp = 2, 0.3\n", 760 | "frequencies = np.array([200, 400, 600, 800, 1000])\n", 761 | "fs = 44100\n", 762 | "t = np.arange(np.ceil(dur * fs)) / fs\n", 763 | "t.shape = -1, 1\n", 764 | "t" 765 | ] 766 | }, 767 | { 768 | "cell_type": "code", 769 | "execution_count": 33, 770 | "metadata": { 771 | "execution": { 772 | "iopub.execute_input": "2020-11-26T13:08:38.867386Z", 773 | "iopub.status.busy": "2020-11-26T13:08:38.866823Z", 774 | "iopub.status.idle": "2020-11-26T13:08:38.869352Z", 775 | "shell.execute_reply": "2020-11-26T13:08:38.868932Z" 776 | } 777 | }, 778 | "outputs": [ 779 | { 780 | "data": { 781 | "text/plain": [ 782 | "array([0.3 , 0.15 , 0.1 , 0.075, 0.06 ])" 783 | ] 784 | }, 785 | "execution_count": 1, 786 | "metadata": {}, 787 | "output_type": "execute_result" 788 | } 789 | ], 790 | "source": [ 791 | "amplitudes = amp * 1 / np.arange(1, len(frequencies)+1)\n", 792 | "amplitudes" 793 | ] 794 | }, 795 | { 796 | "cell_type": "code", 797 | "execution_count": 34, 798 | "metadata": { 799 | "execution": { 800 | "iopub.execute_input": "2020-11-26T13:08:38.872011Z", 801 | "iopub.status.busy": "2020-11-26T13:08:38.871614Z", 802 | "iopub.status.idle": "2020-11-26T13:08:38.883250Z", 803 | "shell.execute_reply": "2020-11-26T13:08:38.883650Z" 804 | } 805 | }, 806 | "outputs": [ 807 | { 808 | "data": { 809 | "text/plain": [ 810 | "(88200, 5)" 811 | ] 812 | }, 813 | "execution_count": 1, 814 | "metadata": {}, 815 | "output_type": "execute_result" 816 | } 817 | ], 818 | "source": [ 819 | "five_sines = amplitudes * np.sin(2 * np.pi * frequencies * t)\n", 820 | "five_sines.shape" 821 | ] 822 | }, 823 | { 824 | "cell_type": "code", 825 | "execution_count": 35, 826 | "metadata": { 827 | "execution": { 828 | "iopub.execute_input": "2020-11-26T13:08:38.886322Z", 829 | "iopub.status.busy": "2020-11-26T13:08:38.885903Z", 830 | "iopub.status.idle": "2020-11-26T13:08:38.889360Z", 831 | "shell.execute_reply": "2020-11-26T13:08:38.889764Z" 832 | } 833 | }, 834 | "outputs": [], 835 | "source": [ 836 | "sum_of_sines = five_sines.sum(axis=1)" 837 | ] 838 | }, 839 | { 840 | "cell_type": "code", 841 | "execution_count": 36, 842 | "metadata": { 843 | "execution": { 844 | "iopub.execute_input": "2020-11-26T13:08:38.892232Z", 845 | "iopub.status.busy": "2020-11-26T13:08:38.891833Z", 846 | "iopub.status.idle": "2020-11-26T13:08:38.899304Z", 847 | "shell.execute_reply": "2020-11-26T13:08:38.898887Z" 848 | } 849 | }, 850 | "outputs": [], 851 | "source": [ 852 | "myplot(sum_of_sines)" 853 | ] 854 | }, 855 | { 856 | "cell_type": "code", 857 | "execution_count": 37, 858 | "metadata": { 859 | "execution": { 860 | "iopub.execute_input": "2020-11-26T13:08:38.902259Z", 861 | "iopub.status.busy": "2020-11-26T13:08:38.901749Z", 862 | "iopub.status.idle": "2020-11-26T13:08:38.907458Z", 863 | "shell.execute_reply": "2020-11-26T13:08:38.907864Z" 864 | } 865 | }, 866 | "outputs": [], 867 | "source": [ 868 | "myplay(five_sines[:, [0, 1, 2, 3, 4]].sum(axis=1))" 869 | ] 870 | }, 871 | { 872 | "cell_type": "code", 873 | "execution_count": 38, 874 | "metadata": { 875 | "execution": { 876 | "iopub.execute_input": "2020-11-26T13:08:38.910658Z", 877 | "iopub.status.busy": "2020-11-26T13:08:38.910190Z", 878 | "iopub.status.idle": "2020-11-26T13:08:39.042790Z", 879 | "shell.execute_reply": "2020-11-26T13:08:39.043212Z" 880 | } 881 | }, 882 | "outputs": [], 883 | "source": [ 884 | "myplay(five_sines[:, [0, 1, 2, 3]].sum(axis=1))" 885 | ] 886 | }, 887 | { 888 | "cell_type": "code", 889 | "execution_count": 39, 890 | "metadata": { 891 | "execution": { 892 | "iopub.execute_input": "2020-11-26T13:08:39.046158Z", 893 | "iopub.status.busy": "2020-11-26T13:08:39.045697Z", 894 | "iopub.status.idle": "2020-11-26T13:08:39.179338Z", 895 | "shell.execute_reply": "2020-11-26T13:08:39.179750Z" 896 | } 897 | }, 898 | "outputs": [], 899 | "source": [ 900 | "myplay(five_sines[:, [0, 1, 2, 4]].sum(axis=1))" 901 | ] 902 | }, 903 | { 904 | "cell_type": "code", 905 | "execution_count": 40, 906 | "metadata": { 907 | "execution": { 908 | "iopub.execute_input": "2020-11-26T13:08:39.182506Z", 909 | "iopub.status.busy": "2020-11-26T13:08:39.182030Z", 910 | "iopub.status.idle": "2020-11-26T13:08:39.315455Z", 911 | "shell.execute_reply": "2020-11-26T13:08:39.315005Z" 912 | } 913 | }, 914 | "outputs": [], 915 | "source": [ 916 | "myplay(five_sines[:, [0, 1, 3, 4]].sum(axis=1))" 917 | ] 918 | }, 919 | { 920 | "cell_type": "code", 921 | "execution_count": 41, 922 | "metadata": { 923 | "execution": { 924 | "iopub.execute_input": "2020-11-26T13:08:39.318904Z", 925 | "iopub.status.busy": "2020-11-26T13:08:39.318324Z", 926 | "iopub.status.idle": "2020-11-26T13:08:39.450570Z", 927 | "shell.execute_reply": "2020-11-26T13:08:39.451241Z" 928 | } 929 | }, 930 | "outputs": [], 931 | "source": [ 932 | "myplay(five_sines[:, [0, 2, 3, 4]].sum(axis=1))" 933 | ] 934 | }, 935 | { 936 | "cell_type": "code", 937 | "execution_count": 42, 938 | "metadata": { 939 | "execution": { 940 | "iopub.execute_input": "2020-11-26T13:08:39.454538Z", 941 | "iopub.status.busy": "2020-11-26T13:08:39.454083Z", 942 | "iopub.status.idle": "2020-11-26T13:08:39.586180Z", 943 | "shell.execute_reply": "2020-11-26T13:08:39.585762Z" 944 | } 945 | }, 946 | "outputs": [], 947 | "source": [ 948 | "myplay(five_sines[:, [1, 2, 3, 4]].sum(axis=1))" 949 | ] 950 | }, 951 | { 952 | "cell_type": "markdown", 953 | "metadata": {}, 954 | "source": [ 955 | "" 956 | ] 957 | }, 958 | { 959 | "cell_type": "code", 960 | "execution_count": 43, 961 | "metadata": { 962 | "execution": { 963 | "iopub.execute_input": "2020-11-26T13:08:39.589805Z", 964 | "iopub.status.busy": "2020-11-26T13:08:39.589273Z", 965 | "iopub.status.idle": "2020-11-26T13:08:39.591288Z", 966 | "shell.execute_reply": "2020-11-26T13:08:39.591680Z" 967 | } 968 | }, 969 | "outputs": [ 970 | { 971 | "data": { 972 | "text/plain": [ 973 | "array([ 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000, 2200,\n", 974 | " 2400, 2600, 2800, 3000, 3200, 3400, 3600, 3800, 4000])" 975 | ] 976 | }, 977 | "execution_count": 1, 978 | "metadata": {}, 979 | "output_type": "execute_result" 980 | } 981 | ], 982 | "source": [ 983 | "f0 = 200 # Hz\n", 984 | "partials = 20\n", 985 | "\n", 986 | "frequencies = f0 * np.arange(1, partials + 1)\n", 987 | "frequencies" 988 | ] 989 | }, 990 | { 991 | "cell_type": "code", 992 | "execution_count": 44, 993 | "metadata": { 994 | "execution": { 995 | "iopub.execute_input": "2020-11-26T13:08:39.594964Z", 996 | "iopub.status.busy": "2020-11-26T13:08:39.594484Z", 997 | "iopub.status.idle": "2020-11-26T13:08:39.596852Z", 998 | "shell.execute_reply": "2020-11-26T13:08:39.597252Z" 999 | } 1000 | }, 1001 | "outputs": [ 1002 | { 1003 | "data": { 1004 | "text/plain": [ 1005 | "array([0.3 , 0.15 , 0.1 , 0.075 , 0.06 ,\n", 1006 | " 0.05 , 0.04285714, 0.0375 , 0.03333333, 0.03 ,\n", 1007 | " 0.02727273, 0.025 , 0.02307692, 0.02142857, 0.02 ,\n", 1008 | " 0.01875 , 0.01764706, 0.01666667, 0.01578947, 0.015 ])" 1009 | ] 1010 | }, 1011 | "execution_count": 1, 1012 | "metadata": {}, 1013 | "output_type": "execute_result" 1014 | } 1015 | ], 1016 | "source": [ 1017 | "amplitudes = amp * 1 / np.arange(1, len(frequencies)+1)\n", 1018 | "amplitudes" 1019 | ] 1020 | }, 1021 | { 1022 | "cell_type": "code", 1023 | "execution_count": 45, 1024 | "metadata": { 1025 | "execution": { 1026 | "iopub.execute_input": "2020-11-26T13:08:39.599946Z", 1027 | "iopub.status.busy": "2020-11-26T13:08:39.599545Z", 1028 | "iopub.status.idle": "2020-11-26T13:08:39.634983Z", 1029 | "shell.execute_reply": "2020-11-26T13:08:39.635372Z" 1030 | } 1031 | }, 1032 | "outputs": [ 1033 | { 1034 | "data": { 1035 | "text/plain": [ 1036 | "(88200, 20)" 1037 | ] 1038 | }, 1039 | "execution_count": 1, 1040 | "metadata": {}, 1041 | "output_type": "execute_result" 1042 | } 1043 | ], 1044 | "source": [ 1045 | "many_sines = amplitudes * np.sin(2 * np.pi * frequencies * t)\n", 1046 | "many_sines.shape" 1047 | ] 1048 | }, 1049 | { 1050 | "cell_type": "code", 1051 | "execution_count": 46, 1052 | "metadata": { 1053 | "execution": { 1054 | "iopub.execute_input": "2020-11-26T13:08:39.638132Z", 1055 | "iopub.status.busy": "2020-11-26T13:08:39.637712Z", 1056 | "iopub.status.idle": "2020-11-26T13:08:39.641982Z", 1057 | "shell.execute_reply": "2020-11-26T13:08:39.641451Z" 1058 | } 1059 | }, 1060 | "outputs": [], 1061 | "source": [ 1062 | "sawtooth = many_sines.sum(axis=1)" 1063 | ] 1064 | }, 1065 | { 1066 | "cell_type": "code", 1067 | "execution_count": 47, 1068 | "metadata": { 1069 | "execution": { 1070 | "iopub.execute_input": "2020-11-26T13:08:39.644788Z", 1071 | "iopub.status.busy": "2020-11-26T13:08:39.644338Z", 1072 | "iopub.status.idle": "2020-11-26T13:08:39.651347Z", 1073 | "shell.execute_reply": "2020-11-26T13:08:39.650906Z" 1074 | } 1075 | }, 1076 | "outputs": [], 1077 | "source": [ 1078 | "myplot(sawtooth)" 1079 | ] 1080 | }, 1081 | { 1082 | "cell_type": "code", 1083 | "execution_count": 48, 1084 | "metadata": { 1085 | "execution": { 1086 | "iopub.execute_input": "2020-11-26T13:08:39.654073Z", 1087 | "iopub.status.busy": "2020-11-26T13:08:39.653665Z", 1088 | "iopub.status.idle": "2020-11-26T13:08:39.786334Z", 1089 | "shell.execute_reply": "2020-11-26T13:08:39.786783Z" 1090 | } 1091 | }, 1092 | "outputs": [], 1093 | "source": [ 1094 | "myplay(sawtooth)" 1095 | ] 1096 | }, 1097 | { 1098 | "cell_type": "markdown", 1099 | "metadata": {}, 1100 | "source": [ 1101 | "https://en.wikipedia.org/wiki/Sawtooth_wave" 1102 | ] 1103 | }, 1104 | { 1105 | "cell_type": "code", 1106 | "execution_count": 49, 1107 | "metadata": { 1108 | "execution": { 1109 | "iopub.execute_input": "2020-11-26T13:08:39.789365Z", 1110 | "iopub.status.busy": "2020-11-26T13:08:39.788974Z", 1111 | "iopub.status.idle": "2020-11-26T13:08:39.792598Z", 1112 | "shell.execute_reply": "2020-11-26T13:08:39.792176Z" 1113 | } 1114 | }, 1115 | "outputs": [], 1116 | "source": [ 1117 | "square = many_sines[:, ::2].sum(axis=1)" 1118 | ] 1119 | }, 1120 | { 1121 | "cell_type": "code", 1122 | "execution_count": 50, 1123 | "metadata": { 1124 | "execution": { 1125 | "iopub.execute_input": "2020-11-26T13:08:39.795020Z", 1126 | "iopub.status.busy": "2020-11-26T13:08:39.794619Z", 1127 | "iopub.status.idle": "2020-11-26T13:08:39.801575Z", 1128 | "shell.execute_reply": "2020-11-26T13:08:39.801161Z" 1129 | } 1130 | }, 1131 | "outputs": [], 1132 | "source": [ 1133 | "myplot(square)" 1134 | ] 1135 | }, 1136 | { 1137 | "cell_type": "code", 1138 | "execution_count": 51, 1139 | "metadata": { 1140 | "execution": { 1141 | "iopub.execute_input": "2020-11-26T13:08:39.804213Z", 1142 | "iopub.status.busy": "2020-11-26T13:08:39.803797Z", 1143 | "iopub.status.idle": "2020-11-26T13:08:39.936201Z", 1144 | "shell.execute_reply": "2020-11-26T13:08:39.935736Z" 1145 | } 1146 | }, 1147 | "outputs": [], 1148 | "source": [ 1149 | "myplay(square)" 1150 | ] 1151 | }, 1152 | { 1153 | "cell_type": "markdown", 1154 | "metadata": {}, 1155 | "source": [ 1156 | "https://en.wikipedia.org/wiki/Square_wave" 1157 | ] 1158 | }, 1159 | { 1160 | "cell_type": "markdown", 1161 | "metadata": {}, 1162 | "source": [ 1163 | "

\n", 1164 | " \n", 1166 | " \"CC0\"\n", 1167 | " \n", 1168 | "
\n", 1169 | " To the extent possible under law,\n", 1170 | " the person who associated CC0\n", 1171 | " with this work has waived all copyright and related or neighboring\n", 1172 | " rights to this work.\n", 1173 | "

" 1174 | ] 1175 | } 1176 | ], 1177 | "metadata": { 1178 | "kernelspec": { 1179 | "display_name": "mystiasp", 1180 | "language": "python", 1181 | "name": "mystiasp" 1182 | }, 1183 | "language_info": { 1184 | "codemirror_mode": { 1185 | "name": "ipython", 1186 | "version": 3 1187 | }, 1188 | "file_extension": ".py", 1189 | "mimetype": "text/x-python", 1190 | "name": "python", 1191 | "nbconvert_exporter": "python", 1192 | "pygments_lexer": "ipython3", 1193 | "version": "3.7.8" 1194 | } 1195 | }, 1196 | "nbformat": 4, 1197 | "nbformat_minor": 1 1198 | } 1199 | -------------------------------------------------------------------------------- /linear_systems_I-solutions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Linear Systems I - Solutions\n", 8 | "\n", 9 | "[back to exercise](linear_systems_I.ipynb)\n", 10 | "\n", 11 | "## Preparations" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": { 18 | "execution": { 19 | "iopub.execute_input": "2020-11-26T13:08:45.486618Z", 20 | "iopub.status.busy": "2020-11-26T13:08:45.486182Z", 21 | "iopub.status.idle": "2020-11-26T13:08:45.849175Z", 22 | "shell.execute_reply": "2020-11-26T13:08:45.849580Z" 23 | } 24 | }, 25 | "outputs": [], 26 | "source": [ 27 | "import tools\n", 28 | "import sounddevice as sd # for playback\n", 29 | "import soundfile as sf # for reading a soundfile" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "And some other stuff:" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 2, 42 | "metadata": { 43 | "execution": { 44 | "iopub.execute_input": "2020-11-26T13:08:45.852847Z", 45 | "iopub.status.busy": "2020-11-26T13:08:45.852392Z", 46 | "iopub.status.idle": "2020-11-26T13:08:46.000164Z", 47 | "shell.execute_reply": "2020-11-26T13:08:45.999709Z" 48 | } 49 | }, 50 | "outputs": [], 51 | "source": [ 52 | "# remove \"inline\" to get a separate plotting window:\n", 53 | "import numpy as np\n", 54 | "import matplotlib.pyplot as plt\n", 55 | "%matplotlib inline" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "## One-dimensional time-continuous Systems\n", 63 | "\n", 64 | "First, we will have a quick review on linear systems in one dimension. In our case, we will use time signals depending on $t \\in \\mathbb{R}$. Generally, the input signal $x(t) \\in \\mathbb{C}$ and the corresponding output signal $y(t) \\in \\mathbb{C}$ of a system $\\mathcal H$ are related via:\n", 65 | "\n", 66 | "$$y(t) = \\mathcal{H}\\{x(t)\\}\\,.$$\n", 67 | "\n", 68 | "### Linear Time-Invariant (LTI) Systems\n", 69 | "\n", 70 | "As simple as it sounds, LTI-system are linear and time-invariant\n", 71 | "\n", 72 | "#### Linearity\n", 73 | "\n", 74 | "*Exercise*: Explain the term \"linear\" in your own words. \n", 75 | " \n", 76 | "\n", 77 | "*\n", 78 | "The system response (output signal) to a linear combination of different input signals is equal to the linear combination of the system responses to each invididual input signal\n", 79 | "*\n", 80 | " \n", 81 | "\n", 82 | "*Exercise*: What does this mean mathematically?\n", 83 | "\n", 84 | "\n", 85 | "$$\\mathcal{H}\\{A \\cdot x_1(t) + B \\cdot x_2(t)\\} = A \\cdot \\mathcal{H}\\{x_1(t)\\} + B \\cdot \\mathcal{H}\\{x_2(t)\\}\\, \\text{ for all } A,B \\in \\mathbb{C}$$\n", 86 | "\n" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "#### Time-Invariance\n", 94 | "\n", 95 | "*Exercise*: Explain the term \"time-invariance\" in your own words.\n", 96 | "\n", 97 | "*\n", 98 | "The properties of the system to not change over time: If a distinct input signal causes corresponding system response, a delayed version of that input signal results a delayed version of the system responses.\n", 99 | "*\n", 100 | "\n", 101 | "\n", 102 | "*Exercise*: What does this mean mathematically?\n", 103 | "\n", 104 | "\n", 105 | "$$\\mathcal{H}\\{x(t-\\tau)\\} = y(t-\\tau)\\, \\text{ for all } \\tau \\in \\mathbb{R}$$\n", 106 | "\n", 107 | "\n", 108 | "if $y(t) = \\mathcal{H} \\{ x(t) \\}$ is known.\n", 109 | "\n", 110 | "#### Are these systems LTI?\n", 111 | "\n", 112 | "*Exercise*: Vote for your LTI-system.\n", 113 | "\n", 114 | "1. $\\displaystyle y(t) = a \\cdot x(t) $ with $a \\in \\mathbb{C}$ \n", 115 | " Yes: \n", 116 | " No: \n", 117 | " Result: Yes\n", 118 | "2. $\\displaystyle y(t) = a \\cdot x(t) + b $ with $a,b \\in \\mathbb{C}$ \n", 119 | " Yes: \n", 120 | " No: \n", 121 | " Result: No: time-invariant but non-linear \n", 122 | "3. $\\displaystyle y(t) = a \\cdot x(t-t_0) $ with $a \\in \\mathbb{C}$ and $t_0 \\in \\mathbb{R}$ \n", 123 | " Yes: \n", 124 | " No: \n", 125 | " Result: Yes \n", 126 | "4. $\\displaystyle y(t) = a \\cdot x(t-b \\cdot t) $ with $a \\in \\mathbb{C}$ and $b \\in \\mathbb{R}$ \n", 127 | " Yes: \n", 128 | " No: \n", 129 | " Result: No: linear, but time-variant \n", 130 | "5. $\\displaystyle y(t) = \\frac{\\mathrm d x(t)}{\\mathrm d t}$ \n", 131 | " Yes: \n", 132 | " No: \n", 133 | " Result: Yes \n", 134 | "6. $\\displaystyle y(t) = \\int x(t)\\,\\mathrm d t $ \n", 135 | " Yes: \n", 136 | " No: \n", 137 | " Result: Yes \n", 138 | "7. $\\displaystyle y(t) = \\int_{-\\infty}^{\\infty} h(t_0) \\cdot x(t - t_0)\\,\\mathrm d t_0 $ \n", 139 | " Yes: \n", 140 | " No: \n", 141 | " Result: Yes\n", 142 | " \n", 143 | "#### Listen to a linear and a non-linear system\n", 144 | "\n", 145 | "We will investigate two unknown systems. The only information we have about these systems is that the first is LTI (linear and time invariant) and the second is non-linear. They are defined by the functions `tools.blackbox()` and `tools.blackbox_nonlinear()`. Have a quick look at the documentation:" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 3, 151 | "metadata": { 152 | "execution": { 153 | "iopub.execute_input": "2020-11-26T13:08:46.019826Z", 154 | "iopub.status.busy": "2020-11-26T13:08:46.019428Z", 155 | "iopub.status.idle": "2020-11-26T13:08:46.041818Z", 156 | "shell.execute_reply": "2020-11-26T13:08:46.042290Z" 157 | } 158 | }, 159 | "outputs": [], 160 | "source": [ 161 | "tools.blackbox?" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": 4, 167 | "metadata": { 168 | "execution": { 169 | "iopub.execute_input": "2020-11-26T13:08:46.046371Z", 170 | "iopub.status.busy": "2020-11-26T13:08:46.045941Z", 171 | "iopub.status.idle": "2020-11-26T13:08:46.048446Z", 172 | "shell.execute_reply": "2020-11-26T13:08:46.048030Z" 173 | } 174 | }, 175 | "outputs": [], 176 | "source": [ 177 | "tools.blackbox_nonlinear?" 178 | ] 179 | }, 180 | { 181 | "cell_type": "markdown", 182 | "metadata": {}, 183 | "source": [ 184 | "*Exercise:* Load the audio file [data/xmas.wav](data/xmas.wav) and apply both functions to it." 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": 5, 190 | "metadata": { 191 | "execution": { 192 | "iopub.execute_input": "2020-11-26T13:08:46.053254Z", 193 | "iopub.status.busy": "2020-11-26T13:08:46.052857Z", 194 | "iopub.status.idle": "2020-11-26T13:08:46.055205Z", 195 | "shell.execute_reply": "2020-11-26T13:08:46.054777Z" 196 | } 197 | }, 198 | "outputs": [], 199 | "source": [ 200 | "# how to read an audio file\n", 201 | "sf.read?" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 6, 207 | "metadata": { 208 | "execution": { 209 | "iopub.execute_input": "2020-11-26T13:08:46.058067Z", 210 | "iopub.status.busy": "2020-11-26T13:08:46.057618Z", 211 | "iopub.status.idle": "2020-11-26T13:08:46.073575Z", 212 | "shell.execute_reply": "2020-11-26T13:08:46.073977Z" 213 | } 214 | }, 215 | "outputs": [], 216 | "source": [ 217 | "[x, fs] = sf.read('data/xmas.wav')\n", 218 | "\n", 219 | "ylin = tools.blackbox(x, fs)\n", 220 | "ynonlin = tools.blackbox_nonlinear(x, fs)" 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "metadata": {}, 226 | "source": [ 227 | "*Exercise*: Listen to the input signal and both output signals.\n", 228 | "\n", 229 | "**CAUTION: CHECK THE PLAYBACk LEVEL**" 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": 7, 235 | "metadata": { 236 | "execution": { 237 | "iopub.execute_input": "2020-11-26T13:08:46.078109Z", 238 | "iopub.status.busy": "2020-11-26T13:08:46.077670Z", 239 | "iopub.status.idle": "2020-11-26T13:08:46.079231Z", 240 | "shell.execute_reply": "2020-11-26T13:08:46.079653Z" 241 | } 242 | }, 243 | "outputs": [], 244 | "source": [ 245 | "# how to play back the signal\n", 246 | "sd.play?" 247 | ] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": 8, 252 | "metadata": { 253 | "execution": { 254 | "iopub.execute_input": "2020-11-26T13:08:46.082755Z", 255 | "iopub.status.busy": "2020-11-26T13:08:46.082325Z", 256 | "iopub.status.idle": "2020-11-26T13:08:54.287531Z", 257 | "shell.execute_reply": "2020-11-26T13:08:54.287945Z" 258 | } 259 | }, 260 | "outputs": [], 261 | "source": [ 262 | "sd.play(x, blocking=True)\n", 263 | "sd.play(ylin, blocking=True)\n", 264 | "sd.play(ynonlin, blocking=True)" 265 | ] 266 | }, 267 | { 268 | "cell_type": "markdown", 269 | "metadata": {}, 270 | "source": [ 271 | "### The Impulse Response\n", 272 | "\n", 273 | "The impulse response $h(t)$ of an LTI system characterises it completely. It is the LTI system's response to a Dirac impulse $\\delta(t)$\n", 274 | "\n", 275 | "$$h(t) = \\mathcal{H}\\{\\delta(t)\\}\\,.$$\n", 276 | "\n", 277 | "So why is the impulse response sufficient to describe the whole LTI system? The input signal $x(t)$ can be described as a sequence of Dirac impulses \n", 278 | "\n", 279 | "$$ x(t) = \\int_{-\\infty}^{\\infty} x(t_0) \\cdot \\delta(t-t_0)\\,\\mathrm d t_0\\,,$$ \n", 280 | "\n", 281 | "where the Dirac impulse at $t_0$ is weighted by the value $x(t_0)$ of the signal at $t_0$. Applying the system onto $x(t)$ yields\n", 282 | "\n", 283 | "$$ y(t) = \\mathcal{H}\\{x(t)\\} = \\mathcal{H}\\left\\{\\int_{-\\infty}^{\\infty} x(t_0) \\cdot \\delta(t-t_0)\\,\\mathrm d t_0\\right\\}$$\n", 284 | "\n", 285 | "As a next step, we can exchange the integral operator $\\int$ and the system operator $\\mathcal{H}$:\n", 286 | "\n", 287 | "$$ y(t) = \\mathcal{H}\\{x(t)\\} = \\int_{-\\infty}^{\\infty} x(t_0) \\cdot \\mathcal{H}\\{\\delta(t-t_0)\\}\\,\\mathrm d t_0$$\n", 288 | "\n", 289 | "*Exercise*: What property has to be fulfilled by $\\mathcal{H}$ in order to be able to exchange integral and system operator?" 290 | ] 291 | }, 292 | { 293 | "cell_type": "markdown", 294 | "metadata": {}, 295 | "source": [ 296 | "\n", 297 | "*\n", 298 | "Linearity \n", 299 | "*\n", 300 | "" 301 | ] 302 | }, 303 | { 304 | "cell_type": "markdown", 305 | "metadata": {}, 306 | "source": [ 307 | "As the last step, we re-write the system response of the Dirac $\\delta$ shifted about $t_0$ as the shifted impulse response $h(t-t_0)$:\n", 308 | "\n", 309 | "$$ y(t) = \\mathcal{H}\\{x(t)\\} = \\int_{-\\infty}^{\\infty} x(t_0) \\cdot h(t-t_0)\\,\\mathrm d t_0$$\n", 310 | "\n", 311 | "*Exercise*: What property has to be fulfilled by $\\mathcal{H}$ in order to replace $\\mathcal{H}\\{\\delta(t-t_0)\\}$ by $h(t-t_0)$?" 312 | ] 313 | }, 314 | { 315 | "cell_type": "markdown", 316 | "metadata": {}, 317 | "source": [ 318 | "\n", 319 | "*\n", 320 | "Time-Invariance\n", 321 | "*\n", 322 | "" 323 | ] 324 | }, 325 | { 326 | "cell_type": "markdown", 327 | "metadata": {}, 328 | "source": [ 329 | "Hence, we can describe the output signal $y(t)$ by the so-called **linear convolution** integral of the corresponding input signal $x(t)$ and the impulse response $h(t)$. Its short version reads\n", 330 | "\n", 331 | "$$ y(t) = x(t) * h(t) $$\n", 332 | "\n", 333 | "where $*$ is a common notation of the convolution." 334 | ] 335 | }, 336 | { 337 | "cell_type": "markdown", 338 | "metadata": {}, 339 | "source": [ 340 | "### A Naive Implementation of the Linear Convolution\n", 341 | "\n", 342 | "Time-continuous signals cannot be handled by computers. Signals must be sampled in time with the sample period $T_s$.\n", 343 | "This yields discrete-time signals. The discrete-time counterpart of a linear convolution is given as\n", 344 | "\n", 345 | "$$ y[n] = x[n] \\ast h[n] = \\sum_{k = -\\infty}^{\\infty} x[k] \\cdot h[n-k] $$\n", 346 | "\n", 347 | "where $y[n] = y(n T_s)$, $x[n] = x(n T_s)$, and $h[n] = h(n T_s)$ denote the discrete-time versions of the involved entities.\n", 348 | "\n", 349 | "*Exercise:* Write a function called `naive_convolution()` that computes the convolution of two one-dimensional arrays (discrete-time signals of finite length) by means of two nested loops according to the equation above, where $x$ and $h$ are one-dimensional arrays of finite lengths. The infinite sum can be changed to a finite sum by assuming that all values before index 0 and all values after the last array element are equal to zero.\n", 350 | "\n", 351 | "Following this assumption, at which indices $n$ does $y[n]$ have its first and last non-zero value?" 352 | ] 353 | }, 354 | { 355 | "cell_type": "code", 356 | "execution_count": 9, 357 | "metadata": { 358 | "execution": { 359 | "iopub.execute_input": "2020-11-26T13:08:54.308282Z", 360 | "iopub.status.busy": "2020-11-26T13:08:54.307748Z", 361 | "iopub.status.idle": "2020-11-26T13:08:54.424036Z", 362 | "shell.execute_reply": "2020-11-26T13:08:54.424503Z" 363 | } 364 | }, 365 | "outputs": [ 366 | { 367 | "data": { 368 | "text/plain": [ 369 | "Text(0.5, 0, 'n')" 370 | ] 371 | }, 372 | "execution_count": 1, 373 | "metadata": {}, 374 | "output_type": "execute_result" 375 | }, 376 | { 377 | "data": { 378 | "image/png": "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\n", 379 | "text/plain": [ 380 | "
" 381 | ] 382 | }, 383 | "metadata": { 384 | "needs_background": "light" 385 | }, 386 | "output_type": "display_data" 387 | } 388 | ], 389 | "source": [ 390 | "def naive_convolution(x, h):\n", 391 | " # in python, you have to indent everything inside your function\n", 392 | "\n", 393 | " Nx = len(x) # length of x\n", 394 | " Nh = len(h) # length of h\n", 395 | " Ny = Nx + Nh - 1 # resulting length of y\n", 396 | "\n", 397 | " y = np.zeros(Ny) # initialise output array\n", 398 | " for k in np.arange(0, Nx): # help: for which indices k is x non-zero?\n", 399 | " for n in np.arange(k, Nh+k): # help: for which indices (n-k) is h non-zero?\n", 400 | " y[n] = y[n] + x[k] * h[n-k]\n", 401 | "\n", 402 | " return y\n", 403 | " # end of function\n", 404 | "\n", 405 | "\n", 406 | "# try out your function\n", 407 | "x = np.array([1, 1, 1, 1, 1])\n", 408 | "h = np.array([1, 1, 1])\n", 409 | "\n", 410 | "y = naive_convolution(x, h)\n", 411 | "plt.stem(y, use_line_collection=True, basefmt='C0:')\n", 412 | "plt.ylabel('y[n]')\n", 413 | "plt.xlabel('n');" 414 | ] 415 | }, 416 | { 417 | "cell_type": "markdown", 418 | "metadata": {}, 419 | "source": [ 420 | "### The Transfer Function\n", 421 | "\n" 422 | ] 423 | }, 424 | { 425 | "cell_type": "markdown", 426 | "metadata": {}, 427 | "source": [ 428 | "The transfer function of an LTI-system is the temporal Fourier transform its impulse response" 429 | ] 430 | }, 431 | { 432 | "cell_type": "markdown", 433 | "metadata": {}, 434 | "source": [ 435 | "$$ H(\\omega) = \\int_{-\\infty}^{\\infty} h(t) e^{-j\\omega t} \\mathrm d t$$" 436 | ] 437 | }, 438 | { 439 | "cell_type": "markdown", 440 | "metadata": {}, 441 | "source": [ 442 | "*Exercise*: $y(t)$, $x(t)$ and $h(t)$ are related to each other by the convolution. How are the respective Fourier spectra $Y(\\omega)$, $X(\\omega)$, $H(\\omega)$ related?" 443 | ] 444 | }, 445 | { 446 | "cell_type": "markdown", 447 | "metadata": {}, 448 | "source": [ 449 | " " 450 | ] 451 | }, 452 | { 453 | "cell_type": "markdown", 454 | "metadata": {}, 455 | "source": [ 456 | "### A naive implementation of the Fourier Transform\n", 457 | "\n" 458 | ] 459 | }, 460 | { 461 | "cell_type": "markdown", 462 | "metadata": {}, 463 | "source": [ 464 | "Time-continuous signals cannot be handled by computers. They are sampled in time with the sample period $T_s$. In order to compute the Fourier transform of a signal, one has to discretize the integral\n", 465 | "\n", 466 | "$$ H(\\omega) = \\sum_{n=-\\infty}^{\\infty} h[n] e^{-j\\omega n T_s} $$\n", 467 | "\n", 468 | "where $h[n] = h(n T_s)$ denotes the discrete version of the involved $h(t)$. This is also known as DTFT, discrete-time Fourier transform.\n", 469 | "\n", 470 | "*Exercise:* Write a function called `naive_ft()` that computes the Fourier transform of a given signal $x$ of finite length for different frequencies. Use two nested loops according for this, again. The infinite sum can be changed to a finite sum by assuming that all values before index 0 and all values after the last array element are equal to zero." 471 | ] 472 | }, 473 | { 474 | "cell_type": "code", 475 | "execution_count": 10, 476 | "metadata": { 477 | "execution": { 478 | "iopub.execute_input": "2020-11-26T13:08:54.429356Z", 479 | "iopub.status.busy": "2020-11-26T13:08:54.428861Z", 480 | "iopub.status.idle": "2020-11-26T13:08:54.431154Z", 481 | "shell.execute_reply": "2020-11-26T13:08:54.430662Z" 482 | } 483 | }, 484 | "outputs": [], 485 | "source": [ 486 | "def naive_ft(x, f, fs):\n", 487 | " # inputs:\n", 488 | " # x - signal vector\n", 489 | " # f - time-frequencies\n", 490 | " # fs - sample rate\n", 491 | "\n", 492 | " # outputs:\n", 493 | " # X - frequency spectrum\n", 494 | "\n", 495 | " Nsig = len(x) # length of signal\n", 496 | " Nspec = len(f) # length of spectrum\n", 497 | "\n", 498 | " omega = 2*np.pi*f # angular frequency\n", 499 | "\n", 500 | " X = np.zeros(Nspec, dtype=complex) # initialise output spectrum\n", 501 | " for k in np.arange(0, Nspec): # angular frequency\n", 502 | " for n in np.arange(0, Nsig): # help: for which indices (n-k) is h non-zero?\n", 503 | " X[k] = X[k] + x[n]*np.exp(-1j*omega[k]*n/fs)\n", 504 | "\n", 505 | " return X\n", 506 | " # end of function" 507 | ] 508 | }, 509 | { 510 | "cell_type": "markdown", 511 | "metadata": {}, 512 | "source": [ 513 | "*Exercise:* Compute the transfer function of the blackbox system `tools.blackbox`." 514 | ] 515 | }, 516 | { 517 | "cell_type": "code", 518 | "execution_count": 11, 519 | "metadata": { 520 | "execution": { 521 | "iopub.execute_input": "2020-11-26T13:08:54.436783Z", 522 | "iopub.status.busy": "2020-11-26T13:08:54.436143Z", 523 | "iopub.status.idle": "2020-11-26T13:08:55.498636Z", 524 | "shell.execute_reply": "2020-11-26T13:08:55.498221Z" 525 | } 526 | }, 527 | "outputs": [ 528 | { 529 | "data": { 530 | "image/png": "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\n", 531 | "text/plain": [ 532 | "
" 533 | ] 534 | }, 535 | "metadata": { 536 | "needs_background": "light" 537 | }, 538 | "output_type": "display_data" 539 | } 540 | ], 541 | "source": [ 542 | "fs = 44100 # sample rate\n", 543 | "# frequency axis\n", 544 | "f = np.logspace(-4, 0, 100)*fs/2\n", 545 | "# dirac as input signal\n", 546 | "x = np.zeros(2**10)\n", 547 | "x[0] = 1\n", 548 | "\n", 549 | "h = tools.blackbox(x, fs) # impulse response of black system\n", 550 | "H = naive_ft(h, f, fs) # transfer function of blackboard system\n", 551 | "\n", 552 | "plt.semilogx(f, 20*np.log10(abs(H)))\n", 553 | "plt.ylim([-60, 5])\n", 554 | "plt.xlabel('f / Hz')\n", 555 | "plt.ylabel('20 log10 |H| / dB')\n", 556 | "plt.title('DTFT of the blackbox system''s impulse response = Frequency Response')\n", 557 | "plt.grid(True)" 558 | ] 559 | }, 560 | { 561 | "cell_type": "markdown", 562 | "metadata": {}, 563 | "source": [ 564 | "## Solutions\n", 565 | "\n", 566 | "If you had problems solving some of the exercises, don't despair!\n", 567 | "Have a look at the [example solutions](linear_systems-solutions.ipynb)." 568 | ] 569 | }, 570 | { 571 | "cell_type": "markdown", 572 | "metadata": {}, 573 | "source": [ 574 | "

\n", 575 | " \n", 577 | " \"CC0\"\n", 578 | " \n", 579 | "
\n", 580 | " To the extent possible under law,\n", 581 | " the person who associated CC0\n", 582 | " with this work has waived all copyright and related or neighboring\n", 583 | " rights to this work.\n", 584 | "

" 585 | ] 586 | } 587 | ], 588 | "metadata": { 589 | "kernelspec": { 590 | "display_name": "mystiasp", 591 | "language": "python", 592 | "name": "mystiasp" 593 | }, 594 | "language_info": { 595 | "codemirror_mode": { 596 | "name": "ipython", 597 | "version": 3 598 | }, 599 | "file_extension": ".py", 600 | "mimetype": "text/x-python", 601 | "name": "python", 602 | "nbconvert_exporter": "python", 603 | "pygments_lexer": "ipython3", 604 | "version": "3.7.8" 605 | } 606 | }, 607 | "nbformat": 4, 608 | "nbformat_minor": 1 609 | } 610 | -------------------------------------------------------------------------------- /linear_systems_I.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Linear Systems I\n", 8 | "\n", 9 | "[return to main page](index.ipynb)\n", 10 | "\n", 11 | "## Preparations" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": { 18 | "execution": { 19 | "iopub.execute_input": "2020-11-26T13:08:56.568343Z", 20 | "iopub.status.busy": "2020-11-26T13:08:56.567903Z", 21 | "iopub.status.idle": "2020-11-26T13:08:56.926319Z", 22 | "shell.execute_reply": "2020-11-26T13:08:56.926742Z" 23 | } 24 | }, 25 | "outputs": [], 26 | "source": [ 27 | "import tools\n", 28 | "import sounddevice as sd # for playback\n", 29 | "import soundfile as sf # for reading a soundfile" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "And some other stuff:" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 2, 42 | "metadata": { 43 | "execution": { 44 | "iopub.execute_input": "2020-11-26T13:08:56.929940Z", 45 | "iopub.status.busy": "2020-11-26T13:08:56.929507Z", 46 | "iopub.status.idle": "2020-11-26T13:08:57.076082Z", 47 | "shell.execute_reply": "2020-11-26T13:08:57.076493Z" 48 | } 49 | }, 50 | "outputs": [], 51 | "source": [ 52 | "# remove \"inline\" to get a separate plotting window:\n", 53 | "import numpy as np\n", 54 | "import matplotlib.pyplot as plt\n", 55 | "%matplotlib inline" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "## One-dimensional time-continuous Systems\n", 63 | "\n", 64 | "First, we will have a brief review on linear systems in one dimension. In our case, we will use time signals depending on $t \\in \\mathbb{R}$. Generally, the input signal $x(t) \\in \\mathbb{C}$ and the corresponding output signal $y(t) \\in \\mathbb{C}$ of a system $\\mathcal H$ are related via:\n", 65 | "\n", 66 | "$$y(t) = \\mathcal{H}\\{x(t)\\}\\,.$$\n", 67 | "\n", 68 | "### Linear Time-Invariant (LTI) Systems\n", 69 | "\n", 70 | "As simple as it sounds, LTI-system are linear and time-invariant\n", 71 | "\n", 72 | "#### Linearity\n", 73 | "\n", 74 | "*Exercise*: Explain the term \"linear\" in your own words. \n", 75 | " \n", 76 | "*Exercise*: What does this mean mathematically?\n", 77 | "\n", 78 | "$$\\mathcal{H}\\{A \\cdot x_1(t) + B \\cdot x_2(t)\\} = \\,???\\, \\text{ for all } A,B \\in \\mathbb{C}$$\n" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "#### Time-Invariance\n", 86 | "\n", 87 | "*Exercise*: Explain the term \"time-invariance\" in your own words.\n", 88 | "\n", 89 | "*Exercise*: What does this mean mathematically?\n", 90 | "\n", 91 | "$$\\mathcal{H}\\{x(t-\\tau)\\} = \\,???\\, \\text{ for all } \\tau \\in \\mathbb{R}$$\n", 92 | "\n", 93 | "if $y(t) = \\mathcal{H} \\{ x(t) \\}$ is known.\n", 94 | "\n", 95 | "#### Are these systems LTI?\n", 96 | "\n", 97 | "*Exercise*: Vote for your LTI-system.\n", 98 | "\n", 99 | "1. $\\displaystyle y(t) = a \\cdot x(t) $ with $a \\in \\mathbb{C}$ \n", 100 | " Yes: \n", 101 | " No: \n", 102 | " Result:\n", 103 | "2. $\\displaystyle y(t) = a \\cdot x(t) + b $ with $a,b \\in \\mathbb{C}$ \n", 104 | " Yes: \n", 105 | " No: \n", 106 | " Result:\n", 107 | "3. $\\displaystyle y(t) = a \\cdot x(t-t_0) $ with $a \\in \\mathbb{C}$ and $t_0 \\in \\mathbb{R}$ \n", 108 | " Yes: \n", 109 | " No: \n", 110 | " Result:\n", 111 | "4. $\\displaystyle y(t) = a \\cdot x(t-b \\cdot t) $ with $a \\in \\mathbb{C}$ and $b \\in \\mathbb{R}$ \n", 112 | " Yes: \n", 113 | " No: \n", 114 | " Result: \n", 115 | "5. $\\displaystyle y(t) = \\frac{\\mathrm d x(t)}{\\mathrm d t}$ \n", 116 | " Yes: \n", 117 | " No: \n", 118 | " Result: \n", 119 | "6. $\\displaystyle y(t) = \\int x(t)\\,\\mathrm d t $ \n", 120 | " Yes: \n", 121 | " No: \n", 122 | " Result: \n", 123 | "7. $\\displaystyle y(t) = \\int_{-\\infty}^{\\infty} h(t_0) \\cdot x(t - t_0)\\,\\mathrm d t_0 $ \n", 124 | " Yes: \n", 125 | " No: \n", 126 | " Result:\n", 127 | " \n", 128 | "#### Listen to a linear and a non-linear system\n", 129 | "\n", 130 | "We will investigate two unknown systems. The only information we have about these systems is that the first is LTI (linear and time invariant) and the second is non-linear. They are defined by the functions `tools.blackbox()` and `tools.blackbox_nonlinear()`. Have a quick look at the documentation:" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 3, 136 | "metadata": { 137 | "execution": { 138 | "iopub.execute_input": "2020-11-26T13:08:57.085073Z", 139 | "iopub.status.busy": "2020-11-26T13:08:57.084652Z", 140 | "iopub.status.idle": "2020-11-26T13:08:57.118408Z", 141 | "shell.execute_reply": "2020-11-26T13:08:57.118823Z" 142 | } 143 | }, 144 | "outputs": [], 145 | "source": [ 146 | "tools.blackbox?" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": 4, 152 | "metadata": { 153 | "execution": { 154 | "iopub.execute_input": "2020-11-26T13:08:57.121626Z", 155 | "iopub.status.busy": "2020-11-26T13:08:57.121211Z", 156 | "iopub.status.idle": "2020-11-26T13:08:57.124859Z", 157 | "shell.execute_reply": "2020-11-26T13:08:57.124440Z" 158 | } 159 | }, 160 | "outputs": [], 161 | "source": [ 162 | "tools.blackbox_nonlinear?" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "*Exercise:* Load the audio file [data/xmas.wav](data/xmas.wav) and apply both functions to it." 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 5, 175 | "metadata": { 176 | "execution": { 177 | "iopub.execute_input": "2020-11-26T13:08:57.129617Z", 178 | "iopub.status.busy": "2020-11-26T13:08:57.129214Z", 179 | "iopub.status.idle": "2020-11-26T13:08:57.130890Z", 180 | "shell.execute_reply": "2020-11-26T13:08:57.131358Z" 181 | } 182 | }, 183 | "outputs": [], 184 | "source": [ 185 | "# how to read an audio file\n", 186 | "sf.read?" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": null, 192 | "metadata": {}, 193 | "outputs": [], 194 | "source": [] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "*Exercise*: Listen to the input signal and both output signals." 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 6, 206 | "metadata": { 207 | "execution": { 208 | "iopub.execute_input": "2020-11-26T13:08:57.135016Z", 209 | "iopub.status.busy": "2020-11-26T13:08:57.133682Z", 210 | "iopub.status.idle": "2020-11-26T13:08:57.137317Z", 211 | "shell.execute_reply": "2020-11-26T13:08:57.137749Z" 212 | } 213 | }, 214 | "outputs": [], 215 | "source": [ 216 | "# how to play back the signal\n", 217 | "sd.play?" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": null, 223 | "metadata": {}, 224 | "outputs": [], 225 | "source": [] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": {}, 230 | "source": [ 231 | "### The Impulse Response\n", 232 | "\n", 233 | "The impulse response $h(t)$ of an LTI system characterises it completely. It is the LTI system's response to a Dirac impulse $\\delta(t)$\n", 234 | "\n", 235 | "$$h(t) = \\mathcal{H}\\{\\delta(t)\\}\\,.$$\n", 236 | "\n", 237 | "So why is the impulse response sufficient to describe the whole LTI system? The input signal $x(t)$ can be described as a sequence of Dirac impulses \n", 238 | "\n", 239 | "$$ x(t) = \\int_{-\\infty}^{\\infty} x(t_0) \\cdot \\delta(t-t_0)\\,\\mathrm d t_0\\,,$$ \n", 240 | "\n", 241 | "where the Dirac impulse at $t_0$ is weighted by the value $x(t_0)$ of the signal at $t_0$. Applying the system onto $x(t)$ yields\n", 242 | "\n", 243 | "$$ y(t) = \\mathcal{H}\\{x(t)\\} = \\mathcal{H}\\left\\{\\int_{-\\infty}^{\\infty} x(t_0) \\cdot \\delta(t-t_0)\\,\\mathrm d t_0\\right\\}$$\n", 244 | "\n", 245 | "As a next step, we can exchange the integral operator $\\int$ and the system operator $\\mathcal{H}$:\n", 246 | "\n", 247 | "$$ y(t) = \\mathcal{H}\\{x(t)\\} = \\int_{-\\infty}^{\\infty} x(t_0) \\cdot \\mathcal{H}\\{\\delta(t-t_0)\\}\\,\\mathrm d t_0$$\n", 248 | "\n", 249 | "*Exercise*: What property has to be fulfilled by $\\mathcal{H}$ in order to be able to exchange integral and system operator?" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": 7, 255 | "metadata": { 256 | "execution": { 257 | "iopub.execute_input": "2020-11-26T13:08:57.140274Z", 258 | "iopub.status.busy": "2020-11-26T13:08:57.139890Z", 259 | "iopub.status.idle": "2020-11-26T13:08:57.142030Z", 260 | "shell.execute_reply": "2020-11-26T13:08:57.141626Z" 261 | } 262 | }, 263 | "outputs": [], 264 | "source": [ 265 | "# here no code, but explanation" 266 | ] 267 | }, 268 | { 269 | "cell_type": "markdown", 270 | "metadata": {}, 271 | "source": [ 272 | "As the last step, we re-write the system response of the Dirac $\\delta$ shifted about $t_0$ as the shifted impulse response $h(t-t_0)$:\n", 273 | "\n", 274 | "$$ y(t) = \\mathcal{H}\\{x(t)\\} = \\int_{-\\infty}^{\\infty} x(t_0) \\cdot h(t-t_0)\\,\\mathrm d t_0$$\n", 275 | "\n", 276 | "*Exercise*: What property has to be fulfilled by $\\mathcal{H}$ in order to replace $\\mathcal{H}\\{\\delta(t-t_0)\\}$ by $h(t-t_0)$?" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": 8, 282 | "metadata": { 283 | "execution": { 284 | "iopub.execute_input": "2020-11-26T13:08:57.144454Z", 285 | "iopub.status.busy": "2020-11-26T13:08:57.144062Z", 286 | "iopub.status.idle": "2020-11-26T13:08:57.146045Z", 287 | "shell.execute_reply": "2020-11-26T13:08:57.145630Z" 288 | } 289 | }, 290 | "outputs": [], 291 | "source": [ 292 | "# here no code, but explanation " 293 | ] 294 | }, 295 | { 296 | "cell_type": "markdown", 297 | "metadata": {}, 298 | "source": [ 299 | "Hence, we can describe the output signal $y(t)$ by the so-called **linear convolution** integral of the corresponding input signal $x(t)$ and the impulse response $h(t)$. Its short version reads\n", 300 | "\n", 301 | "$$ y(t) = x(t) * h(t) $$\n", 302 | "\n", 303 | "where $*$ is a common notation of the convolution." 304 | ] 305 | }, 306 | { 307 | "cell_type": "markdown", 308 | "metadata": {}, 309 | "source": [ 310 | "### A Naive Implementation of the Linear Convolution\n", 311 | "\n", 312 | "Time-continuous signals cannot be handled by computers. Signals must be sampled in time with the sample period $T_s$.\n", 313 | "This yields discrete-time signals. The discrete-time counterpart of a linear convolution is given as\n", 314 | "\n", 315 | "$$ y[n] = x[n] \\ast h[n] = \\sum_{k = -\\infty}^{\\infty} x[k] \\cdot h[n-k] $$\n", 316 | "\n", 317 | "where $y[n] = y(n T_s)$, $x[n] = x(n T_s)$, and $h[n] = h(n T_s)$ denote the discrete-time versions of the involved entities.\n", 318 | "\n", 319 | "*Exercise:* Write a function called `naive_convolution()` that computes the convolution of two one-dimensional arrays (discrete-time signals of finite length) by means of two nested loops according to the equation above, where $x$ and $h$ are one-dimensional arrays of finite lengths. The infinite sum can be changed to a finite sum by assuming that all values before index 0 and all values after the last array element are equal to zero.\n", 320 | "\n", 321 | "Following this assumption, at which indices $n$ does $y[n]$ have its first and last non-zero value?" 322 | ] 323 | }, 324 | { 325 | "cell_type": "code", 326 | "execution_count": 9, 327 | "metadata": { 328 | "execution": { 329 | "iopub.execute_input": "2020-11-26T13:08:57.149654Z", 330 | "iopub.status.busy": "2020-11-26T13:08:57.149220Z", 331 | "iopub.status.idle": "2020-11-26T13:08:57.151295Z", 332 | "shell.execute_reply": "2020-11-26T13:08:57.150879Z" 333 | } 334 | }, 335 | "outputs": [], 336 | "source": [ 337 | "def naive_convolution(x, h):\n", 338 | " # in python, you have to indent everything inside your function\n", 339 | "\n", 340 | " # Nx = ??? # length of x\n", 341 | " # Nh = ??? # length of h\n", 342 | " # Ny = ??? # resulting length of y\n", 343 | "\n", 344 | " y = np.zeros(Ny) # initialise output array\n", 345 | " # for k in np.arange(???,???): # help: for which indices k is x non-zero?\n", 346 | " # for n in np.arange(???,???): # help: for which indices (n-k) is h non-zero?\n", 347 | " # place the code for the loops here\n", 348 | "\n", 349 | " # end of loop\n", 350 | " # end of loop\n", 351 | " return y\n", 352 | " # end of function\n", 353 | "\n", 354 | "\n", 355 | "# try out your function\n", 356 | "x = np.array([1, 1, 1, 1, 1]) # x\n", 357 | "h = np.array([1, 1, 1]) # h\n", 358 | "\n", 359 | "# y = naive_convolution(x, h)\n", 360 | "# plt.stem(y, use_line_collection=True, basefmt='C0:');\n", 361 | "# do not forget correct labeling of the axes" 362 | ] 363 | }, 364 | { 365 | "cell_type": "markdown", 366 | "metadata": {}, 367 | "source": [ 368 | "### The Transfer Function\n", 369 | "\n" 370 | ] 371 | }, 372 | { 373 | "cell_type": "markdown", 374 | "metadata": {}, 375 | "source": [ 376 | "The transfer function of an LTI-system is the temporal Fourier transform its impulse response" 377 | ] 378 | }, 379 | { 380 | "cell_type": "markdown", 381 | "metadata": {}, 382 | "source": [ 383 | "$$ H(\\omega) = \\int_{-\\infty}^{\\infty} h(t) e^{-j\\omega t} \\mathrm d t$$" 384 | ] 385 | }, 386 | { 387 | "cell_type": "markdown", 388 | "metadata": {}, 389 | "source": [ 390 | "*Exercise*: $y(t)$, $x(t)$ and $h(t)$ are related to each other by the convolution. How are the respective Fourier spectra $Y(\\omega)$, $X(\\omega)$, $H(\\omega)$ related?" 391 | ] 392 | }, 393 | { 394 | "cell_type": "markdown", 395 | "metadata": {}, 396 | "source": [ 397 | " " 398 | ] 399 | }, 400 | { 401 | "cell_type": "markdown", 402 | "metadata": {}, 403 | "source": [ 404 | "### A naive implementation of the Fourier Transform\n", 405 | "\n" 406 | ] 407 | }, 408 | { 409 | "cell_type": "markdown", 410 | "metadata": {}, 411 | "source": [ 412 | "Time-continuous signals can not be easily handled by todays' computers. They are sampled in time with the sample period $T_s$. In order to compute the Fourier transform of a signal, one has to discretize the integral\n", 413 | "\n", 414 | "$$ H(\\omega) = \\sum_{n=-\\infty}^{\\infty} h[n] e^{-j\\omega n T_s} $$\n", 415 | "\n", 416 | "where $h[n] = h(n T_s)$ denotes the discrete version of the involved $h(t)$. This is also known as DTFT, discrete-time Fourier transform.\n", 417 | "\n", 418 | "*Exercise:* Write a function called `naive_ft()` that computes the Fourier transform of a given signal $x$ of finite length for different frequencies, which are also given in an array. Use two nested loops according for this, again. The infinite sum can be changed to a finite sum by assuming that all values before index 0 and all values after the last array element are equal to zero." 419 | ] 420 | }, 421 | { 422 | "cell_type": "code", 423 | "execution_count": 10, 424 | "metadata": { 425 | "execution": { 426 | "iopub.execute_input": "2020-11-26T13:08:57.154565Z", 427 | "iopub.status.busy": "2020-11-26T13:08:57.154159Z", 428 | "iopub.status.idle": "2020-11-26T13:08:57.155796Z", 429 | "shell.execute_reply": "2020-11-26T13:08:57.156183Z" 430 | } 431 | }, 432 | "outputs": [], 433 | "source": [ 434 | "def naive_ft(x, f, fs):\n", 435 | " # inputs:\n", 436 | " # x - signal vector\n", 437 | " # f - time-frequencies\n", 438 | " # fs - sample rate\n", 439 | " \n", 440 | " # outputs:\n", 441 | " # X - frequency spectrum\n", 442 | " \n", 443 | " # Nsig = ??? # length of signal\n", 444 | " # Nspec = ??? # length of spectrum\n", 445 | " \n", 446 | " # omega = 2*np.pi*f # angular frequency\n", 447 | " \n", 448 | " X = np.zeros(Nspec, dtype=complex) # initialise output spectrum\n", 449 | " # for k in np.arange(???, ???): # loop over angular frequencies\n", 450 | " # for n in np.arange(???, ???): # loop over \n", 451 | " # place the code for the loops here\n", 452 | " \n", 453 | " return X\n", 454 | " # end of function" 455 | ] 456 | }, 457 | { 458 | "cell_type": "markdown", 459 | "metadata": {}, 460 | "source": [ 461 | "*Exercise:* Compute the transfer function of the blackbox system `tools.blackbox`." 462 | ] 463 | }, 464 | { 465 | "cell_type": "code", 466 | "execution_count": 11, 467 | "metadata": { 468 | "execution": { 469 | "iopub.execute_input": "2020-11-26T13:08:57.159797Z", 470 | "iopub.status.busy": "2020-11-26T13:08:57.159362Z", 471 | "iopub.status.idle": "2020-11-26T13:08:57.162844Z", 472 | "shell.execute_reply": "2020-11-26T13:08:57.162418Z" 473 | } 474 | }, 475 | "outputs": [], 476 | "source": [ 477 | "fs = 44100; # sample rate\n", 478 | "f = np.logspace(-4, 0, 100)*fs/2; # frequency axis\n", 479 | "# dirac as input signal\n", 480 | "x = np.zeros(2**10)\n", 481 | "x[0] = 1;\n", 482 | "\n", 483 | "h = tools.blackbox(x, fs); # impulse response of black-box system\n", 484 | "# H = naive_ft(h, f, fs); # transfer function of blackboard system\n", 485 | "\n", 486 | "# plt.semilogx(f, 20*np.log10(abs(H)));\n", 487 | "# plt.ylim([-60, 5])\n", 488 | "# do not forget correct labeling of the axes" 489 | ] 490 | }, 491 | { 492 | "cell_type": "markdown", 493 | "metadata": {}, 494 | "source": [ 495 | "## Solutions\n", 496 | "\n", 497 | "If you had problems solving some of the exercises, don't despair!\n", 498 | "Have a look at the [example solutions](linear_systems_I-solutions.ipynb)." 499 | ] 500 | }, 501 | { 502 | "cell_type": "markdown", 503 | "metadata": {}, 504 | "source": [ 505 | "

\n", 506 | " \n", 508 | " \"CC0\"\n", 509 | " \n", 510 | "
\n", 511 | " To the extent possible under law,\n", 512 | " the person who associated CC0\n", 513 | " with this work has waived all copyright and related or neighboring\n", 514 | " rights to this work.\n", 515 | "

" 516 | ] 517 | } 518 | ], 519 | "metadata": { 520 | "kernelspec": { 521 | "display_name": "mystiasp", 522 | "language": "python", 523 | "name": "mystiasp" 524 | }, 525 | "language_info": { 526 | "codemirror_mode": { 527 | "name": "ipython", 528 | "version": 3 529 | }, 530 | "file_extension": ".py", 531 | "mimetype": "text/x-python", 532 | "name": "python", 533 | "nbconvert_exporter": "python", 534 | "pygments_lexer": "ipython3", 535 | "version": "3.7.8" 536 | } 537 | }, 538 | "nbformat": 4, 539 | "nbformat_minor": 1 540 | } 541 | -------------------------------------------------------------------------------- /linear_systems_II-solutions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Linear Systems II\n", 8 | "\n", 9 | "[return to main page](index.ipynb)\n", 10 | "\n", 11 | "## Preparations" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": { 18 | "execution": { 19 | "iopub.execute_input": "2020-11-26T13:10:20.490206Z", 20 | "iopub.status.busy": "2020-11-26T13:10:20.489800Z", 21 | "iopub.status.idle": "2020-11-26T13:10:20.868958Z", 22 | "shell.execute_reply": "2020-11-26T13:10:20.869361Z" 23 | } 24 | }, 25 | "outputs": [ 26 | { 27 | "data": { 28 | "text/plain": [ 29 | "> 0 Built-in Microphone, Core Audio (2 in, 0 out)\n", 30 | "< 1 Built-in Output, Core Audio (0 in, 2 out)\n", 31 | " 2 DisplayPort, Core Audio (0 in, 2 out)\n", 32 | " 3 Aggregate Device, Core Audio (0 in, 2 out)" 33 | ] 34 | }, 35 | "execution_count": 1, 36 | "metadata": {}, 37 | "output_type": "execute_result" 38 | } 39 | ], 40 | "source": [ 41 | "import tools\n", 42 | "import numpy as np\n", 43 | "import sounddevice as sd # for playback\n", 44 | "import soundfile as sf # for reading a soundfile\n", 45 | "from scipy import signal\n", 46 | "sd.query_devices() # check if desired default device is set" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "## Multi-dimensional continuous Systems\n", 54 | "\n", 55 | "In the first part of this exercise, we had a quick review on linear systems in one dimension, namely the time $t$. Now, we add three additional dimension $\\mathbf x = [x,y,z]^T$ in order to cover the three-dimensional space. Generally, the input signal $q \\in \\mathbb{C}$ and the corresponding output signal $p \\in \\mathbb{C}$ of a system $\\mathcal H$ are related via:\n", 56 | "\n", 57 | "$$p(\\mathbf x, t) = p(x,y,z,t) = \\mathcal{H}\\{q(\\mathbf x,t)\\} = \\mathcal{H}\\{q(x,y,z,t)\\}\\,.$$" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "### Linear Time-Space-Invariant (LTSI) Systems\n", 65 | "\n", 66 | "As simple as it sounds, LTI-system are linear, time-invariant and **space-invariant**. We had already covered the first two properties in the [last exercise](linear_systems_I.ipynb#Linear-Time-Invariant-%28LTI%29-Systems)." 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "#### Space-Invariance\n", 74 | "\n", 75 | "*Exercise*: Explain the term \"space-invariance\" in your own words.\n", 76 | "\n", 77 | "*\n", 78 | "The properties of the system to not change over space: If a distinct input signal causes corresponding system response, a shifted version of that input signal results a shifted version of the system responses. \n", 79 | "*\n", 80 | "\n", 81 | "\n", 82 | "*Exercise*: What does this mean mathematically?\n", 83 | "\n", 84 | "\n", 85 | "$$\\mathcal{H}\\{q(\\mathbf x - \\mathbf x_0, t)\\} = \\,p(\\mathbf x - \\mathbf x_0, t)\\, \\text{ for all } \\mathbf x_0 \\in \\mathbb{R}$$\n", 86 | "\n", 87 | "\n", 88 | "if $p(\\mathbf x, t) = \\mathcal{H} \\{ q(\\mathbf x,t) \\}$ is known.\n", 89 | "\n", 90 | "*Exercise*: Can you give an example, for which environment the space invariance does **not** hold?\n", 91 | "\n", 92 | "\n", 93 | "*\n", 94 | "Any ordinary room implying boundary conditions.\n", 95 | "* \n", 96 | "\n" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": {}, 102 | "source": [ 103 | "### The Impulse Response\n", 104 | "\n", 105 | "#### ... of an LTSI System\n", 106 | "\n", 107 | "The impulse response $h(\\mathbf x, t)$ of an LTSI system characterises it completely. It is the system's response to a spatio-temporal Dirac impulse $\\delta(\\mathbf x)\\delta(t)$\n", 108 | "\n", 109 | "$$h(\\mathbf x, t) = h(x, y, z, t) = \\mathcal{H}\\{\\delta(\\mathbf x)\\delta(t)\\} = \\mathcal{H}\\{\\delta(x)\\delta(y)\\delta(z)\\delta(t)\\}\\,.$$\n", 110 | "\n", 111 | "We can describe the output signal $p(\\mathbf x, t)$ by a 4-dimensional convolution of the corresponding input signal $q(\\mathbf x, t)$ and the impulse response $h(\\mathbf x, t)$: \n", 112 | "\n", 113 | "$$ p(\\mathbf x, t) = \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} q(x_0, y_0, z_0, t_0)\\,h(x - x_0, y - y_0, z - z_0, t - t_0)\\,\\mathrm d x_0 \\mathrm d y_0 \\mathrm d z_0 \\mathrm d t_0$$\n", 114 | "\n", 115 | "Its short version reads\n", 116 | "\n", 117 | "$$ p(\\mathbf x, t) = q(\\mathbf x, t) *_{\\mathbf x, t} h(\\mathbf x, t) $$\n", 118 | "\n", 119 | "where $*$ is a common notation of the convolution.\n", 120 | "\n", 121 | "#### ... of an shift-variant LTI System\n", 122 | "\n", 123 | "For a shift-variant system, the impulse response is not independent from the shift in space:\n", 124 | "\n", 125 | "$$h(\\mathbf x, \\mathbf x_0, t) = \\mathcal{H}\\{\\delta(\\mathbf x - \\mathbf x_0)\\delta(t)\\}$$\n", 126 | "\n", 127 | "We can no longer describe the resulting output signal as a convolution with respect to the three dimensions in space:\n", 128 | "\n", 129 | "$$ p(\\mathbf x, t) = \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} q(x_0, y_0, z_0, t_0)\\,h(x,x_0,y,y_0,z,z_0,t-t_0)\\,\\mathrm d x_0\\,\\mathrm d y_0\\,\\mathrm d z_0\\,\\mathrm d t_0$$\n", 130 | "\n", 131 | "The integral with respect to time is still a linear convolution, though.\n" 132 | ] 133 | }, 134 | { 135 | "cell_type": "markdown", 136 | "metadata": {}, 137 | "source": [ 138 | "### The Spatial Frequency Response\n", 139 | "\n", 140 | "For an LTSI-System, the spatial transfer characteristics in terms of its frequency response is defined as the three-dimensional Fourier transform with respect to each dimension in space:\n", 141 | "\n", 142 | "$$ \\tilde h(\\mathbf k, t) = \\tilde h(k_x, k_y, k_z, t) = \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} h(x,y,z,t) e^{+jk_x x} e^{+jk_y y} e^{+jk_z z} \\,\\mathrm d x\\,\\mathrm d y\\,\\mathrm d z$$\n", 143 | "\n", 144 | "with the wave vector $\\mathbf k = [k_x, k_y, k_z]^T$. The Fourier transform with respect to one dimension is equivalent to the temporal Fourier transform with e.g. $\\omega \\rightarrow -k_x$.\n", 145 | "\n", 146 | "### The Spatio-Temporal Frequency Response\n", 147 | "\n", 148 | "Applying the temporal Fourier transform to $\\tilde h(\\mathbf k, t)$ yields the **Spatio-Temporal Transfer Function** of an LTSI-System\n", 149 | "\n", 150 | "$$ \\tilde H(\\mathbf k, \\omega) = \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} h(x,y,z,t) e^{+jk_x x} e^{+jk_y y} e^{+jk_z z} e^{-j\\omega t} \\,\\mathrm d x\\,\\mathrm d y\\,\\mathrm d z\\,\\mathrm d t$$\n", 151 | "\n", 152 | "## Sound Propagation as a Linear System\n", 153 | "\n", 154 | "Remember the inhomogeneous wave equation?\n", 155 | "\n", 156 | "$$\\Delta p(\\mathbf x, t) - \\frac{1}{c^2} \\frac{\\partial^2}{\\partial t^2} p(\\mathbf x, t) = -q(\\mathbf x, t)$$\n", 157 | "\n", 158 | "Its solution $p(\\mathbf x, t)$ has to incorporate boundary conditions and the inhomogenity $q(\\mathbf x, t)$. It describes sound propagation and can be interpreted as an LTI-system with the source density $q(\\mathbf x, t)$ as the input signal and the sound pressure $p(\\mathbf x, t)$ as the output signal. Remember the Green's function? It corresponds to the special inhomogenity $q(\\mathbf x, t) = \\delta(\\mathbf x - \\mathbf x_0)\\delta(t-t_0)$ and fulfils the boundary conditions. The inhomogeneous wave equation for the Green's functions reads\n", 159 | "\n", 160 | "$$\\Delta g(\\mathbf x | \\mathbf x_0 , t) - \\frac{1}{c^2} \\frac{\\partial^2}{\\partial t^2} g(\\mathbf x | \\mathbf x_0 , t) = - \\delta(\\mathbf x - \\mathbf x_0)\\delta(t)\\,.$$\n", 161 | "\n", 162 | "*Exercise*: What is the impulse response $h(\\mathbf x,\\mathbf x_0,t)$ of the wave equation as a linear system?\n", 163 | "\n", 164 | "\n", 165 | "*\n", 166 | "The Green's function represents the impulse response of the wave equation.\n", 167 | "*\n", 168 | "\n", 169 | "\n", 170 | "Under free-field conditions, i.e. no boundary conditions, the wave equation becomes an LTSI-System with free-field Green's function $g_0(\\mathbf x , t) = \\delta(t - |\\mathbf x|/c)$ as its spatio-temporal impulse responses. In rooms, the impulse response is often subsumed under the term **Room Impulse Response**.\n", 171 | "\n", 172 | "### Measuring a Room Impulse Response\n", 173 | "\n", 174 | "#### A Very Simplistic Procedure\n", 175 | "\n", 176 | "There are several possibilities to excite the room with a signal being close to a Dirac - bursting balloons, gunshots, electrical spark discharges, ...; for simplicity (and for safety reasons), one could use two wooden boards which are clapped together. The signal recorded by a microphone standing at the certain positions $\\mathbf x$ then yields the approximate room impulse response $h(\\mathbf x,\\mathbf x_0,t)$\n", 177 | "\n", 178 | "![Two Wooden Boards](data/wooden_boards.jpg)\n", 179 | "\n", 180 | "That's not ideal - ideally all frequencies should be excited equally - but it shall be sufficient for demonstration purposes. As we will get to know later, that there are *much better* ways to measure room impulse responses!" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": 2, 186 | "metadata": { 187 | "execution": { 188 | "iopub.execute_input": "2020-11-26T13:10:20.873308Z", 189 | "iopub.status.busy": "2020-11-26T13:10:20.872887Z", 190 | "iopub.status.idle": "2020-11-26T13:10:27.357020Z", 191 | "shell.execute_reply": "2020-11-26T13:10:27.356572Z" 192 | } 193 | }, 194 | "outputs": [], 195 | "source": [ 196 | "# speech signal\n", 197 | "speech, fs = sf.read(\"data/xmas.wav\")\n", 198 | "# sloppy measured room impulse response\n", 199 | "# (measured with clap seen above, do not harm your ears!)\n", 200 | "rir, fs_rir = sf.read(\"data/rir_clap.wav\")\n", 201 | "\n", 202 | "assert fs == fs_rir # compare sample rates of signal and rir\n", 203 | "speech_clap = signal.fftconvolve(speech, rir) # convolve with respect to time\n", 204 | "# normalize to the same maximum value as the original speech signal:\n", 205 | "speech_clap = tools.normalize(speech_clap, np.max(np.abs(speech)))\n", 206 | "\n", 207 | "sd.play(speech, fs, blocking=True)\n", 208 | "sd.play(speech_clap, fs, blocking=True)" 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "metadata": {}, 214 | "source": [ 215 | "#### More elaborated procedures\n", 216 | "\n", 217 | "In slightly more modern *sweep method*, room is excited with a sine sweep, which is reproduced by means of a loudspeaker. The actual impulse response will be calculated from the excitation signal and the signal recorded by the microphone. Let's listen to some sweep signals (**watch the volume!**):" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": 3, 223 | "metadata": { 224 | "execution": { 225 | "iopub.execute_input": "2020-11-26T13:10:27.361040Z", 226 | "iopub.status.busy": "2020-11-26T13:10:27.360475Z", 227 | "iopub.status.idle": "2020-11-26T13:10:33.644624Z", 228 | "shell.execute_reply": "2020-11-26T13:10:33.644130Z" 229 | } 230 | }, 231 | "outputs": [], 232 | "source": [ 233 | "fstart = 65 # start with 65 Hertz, be nice to your onboard loudspeakers\n", 234 | "fstop = 15000 # stop a 15000 Hertz\n", 235 | "tlen = 3 # length of sweep signal\n", 236 | "t = np.arange(0, tlen*fs)/fs # samples\n", 237 | "\n", 238 | "sweep_lin = signal.chirp(t, fstart, tlen, fstop, method='linear')\n", 239 | "sweep_log = signal.chirp(t, fstart, tlen, fstop, method='logarithmic')\n", 240 | "\n", 241 | "sd.play(sweep_lin, fs, blocking=True)\n", 242 | "sd.play(sweep_log, fs, blocking=True)" 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "metadata": {}, 248 | "source": [ 249 | "## Fourier Analysis of a Sound Field\n", 250 | "\n", 251 | "### Spatial-Temporal Fourier Transform of a Plane Wave\n", 252 | "\n", 253 | "We already got to know the temporal spectrum of a [plane wave](physics_of_sound_I-solutions.ipynb#Plane-Wave) with a direction of propagation $\\mathbf n_{\\mathrm {pw}}$ which reads\n", 254 | "\n", 255 | "$$P_{pw}(\\mathbf x, \\omega) = \\mathrm{exp}\\left(-j\\frac{\\omega}{c} \\mathbf n_{\\mathrm {pw}} \\cdot \\mathbf x \\right) = \\mathrm{exp}\\left(-j\\frac{\\omega}{c} (n_{\\mathrm {pw},x} \\cdot x + n_{\\mathrm {pw},y} \\cdot y + n_{\\mathrm {pw},z} \\cdot z) \\right) \\,. $$\n", 256 | "\n", 257 | "*Exercise*: Calculate the three-dimensional, spatial Fourier Transform $\\tilde P_{pw}(\\mathbf k, \\omega)$ of the Plane Wave. Use the integral identity:\n", 258 | "\n", 259 | "$$ \\int_{-\\infty}^{\\infty} e^{+j a b}\\,\\mathrm d b\n", 260 | " = \\int_{-\\infty}^{\\infty} \\mathrm{exp}\\left(+j a b\\right)\\,\\mathrm d b \n", 261 | " = 2\\pi \\delta(a) \n", 262 | "$$" 263 | ] 264 | }, 265 | { 266 | "cell_type": "markdown", 267 | "metadata": { 268 | "collapsed": true 269 | }, 270 | "source": [ 271 | "\n", 272 | "*\n", 273 | "\n", 274 | "As we already have the temporal spectrum of the plane wave we only need to calculate the three integral we respect to the space dimensions:\n", 275 | "\n", 276 | "$$ \\tilde P_{pw}(\\mathbf k, \\omega) = \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} P_{pw}(\\mathbf x, \\omega) e^{+jk_x x} e^{+jk_y y} e^{+jk_z z} \\,\\mathrm d x\\,\\mathrm d y\\,\\mathrm d z\n", 277 | "$$\n", 278 | "\n", 279 | "Inserting the spectrum of the plane wave\n", 280 | "$$\n", 281 | "\\tilde P_{pw}(\\mathbf k, \\omega) = \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} e^{-j\\frac{\\omega}{c} (n_{\\mathrm {pw},x} \\cdot x + n_{\\mathrm {pw},y} \\cdot y + n_{\\mathrm {pw},z} \\cdot z)} e^{+jk_x x} e^{+jk_y y} e^{+jk_z z} \\,\\mathrm d x\\,\\mathrm d y\\,\\mathrm d z\n", 282 | "$$\n", 283 | "\n", 284 | "Splitting up the exponentials and re-arranging the integrals yields\n", 285 | "\n", 286 | "$$\n", 287 | "\\tilde P_{pw}(\\mathbf k, \\omega) = \n", 288 | " \\int_{-\\infty}^{\\infty} \\mathrm{exp}\\left(+j\\left[k_x-\\frac{\\omega}{c} n_{\\mathrm {pw},x}\\right]x\\right) \\,\\mathrm d x \\cdot\n", 289 | " \\int_{-\\infty}^{\\infty} \\mathrm{exp}\\left(+j\\left[k_y-\\frac{\\omega}{c} n_{\\mathrm {pw},y}\\right]y\\right) \\,\\mathrm d y \\cdot\n", 290 | " \\int_{-\\infty}^{\\infty} \\mathrm{exp}\\left(+j\\left[k_z-\\frac{\\omega}{c} n_{\\mathrm {pw},z}\\right]z\\right) \\,\\mathrm d z\n", 291 | "$$\n", 292 | "\n", 293 | "Using the integral indentity for each of the integrals yields\n", 294 | "\n", 295 | "$$\n", 296 | "\\tilde P_{pw}(\\mathbf k, \\omega) = \n", 297 | " 2\\pi \\delta\\left(k_x-\\frac{\\omega}{c} n_{\\mathrm {pw},x}\\right) \\cdot\n", 298 | " 2\\pi \\delta\\left(k_y-\\frac{\\omega}{c} n_{\\mathrm {pw},y}\\right) \\cdot\n", 299 | " 2\\pi \\delta\\left(k_z-\\frac{\\omega}{c} n_{\\mathrm {pw},z}\\right)\n", 300 | " =\n", 301 | " (2\\pi)^3 \\delta\\left(\\mathbf k -\\frac{\\omega}{c} \\mathbf n_{\\mathrm {pw}}\\right)\n", 302 | "$$\n", 303 | "\n", 304 | "*\n", 305 | "\n" 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "metadata": {}, 311 | "source": [ 312 | "### Fourier Analysis using a Plane Wave Decomposition\n", 313 | "\n", 314 | "We now know that the value of $\\tilde P(\\mathbf k, \\omega)$ at $\\mathbf k = \\frac{\\omega}{c} \\mathbf n_{\\mathrm {pw}}$ corresponds to the amplitude of the plane wave propagation in the directions of $\\mathbf n_{\\mathrm {pw}}$. This can be straightforwardly used to compute the so-called plane wave composition:\n", 315 | "\n", 316 | "$$\n", 317 | "\\bar P(\\mathbf n_{\\mathrm {pw}}) = \\tilde P(\\mathbf k, \\omega)|_{\\mathbf k = \\frac{\\omega}{c} \\mathbf n_{\\mathrm {pw}}}\n", 318 | "$$\n", 319 | "\n", 320 | "After all this math, u can lay back and have a look at the demonstration, how to compute a plane wave decompositon out of two microphone signals.\n", 321 | "\n" 322 | ] 323 | }, 324 | { 325 | "cell_type": "markdown", 326 | "metadata": {}, 327 | "source": [ 328 | "

\n", 329 | " \n", 331 | " \"CC0\"\n", 332 | " \n", 333 | "
\n", 334 | " To the extent possible under law,\n", 335 | " the person who associated CC0\n", 336 | " with this work has waived all copyright and related or neighboring\n", 337 | " rights to this work.\n", 338 | "

" 339 | ] 340 | } 341 | ], 342 | "metadata": { 343 | "kernelspec": { 344 | "display_name": "mystiasp", 345 | "language": "python", 346 | "name": "mystiasp" 347 | }, 348 | "language_info": { 349 | "codemirror_mode": { 350 | "name": "ipython", 351 | "version": 3 352 | }, 353 | "file_extension": ".py", 354 | "mimetype": "text/x-python", 355 | "name": "python", 356 | "nbconvert_exporter": "python", 357 | "pygments_lexer": "ipython3", 358 | "version": "3.7.8" 359 | } 360 | }, 361 | "nbformat": 4, 362 | "nbformat_minor": 1 363 | } 364 | -------------------------------------------------------------------------------- /linear_systems_II.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Linear Systems II\n", 8 | "\n", 9 | "[return to main page](index.ipynb)\n", 10 | "\n", 11 | "## Preparations" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": { 18 | "execution": { 19 | "iopub.execute_input": "2020-11-26T13:10:06.061351Z", 20 | "iopub.status.busy": "2020-11-26T13:10:06.060911Z", 21 | "iopub.status.idle": "2020-11-26T13:10:06.491573Z", 22 | "shell.execute_reply": "2020-11-26T13:10:06.491116Z" 23 | } 24 | }, 25 | "outputs": [ 26 | { 27 | "data": { 28 | "text/plain": [ 29 | "> 0 Built-in Microphone, Core Audio (2 in, 0 out)\n", 30 | "< 1 Built-in Output, Core Audio (0 in, 2 out)\n", 31 | " 2 DisplayPort, Core Audio (0 in, 2 out)\n", 32 | " 3 Aggregate Device, Core Audio (0 in, 2 out)" 33 | ] 34 | }, 35 | "execution_count": 1, 36 | "metadata": {}, 37 | "output_type": "execute_result" 38 | } 39 | ], 40 | "source": [ 41 | "import tools\n", 42 | "import numpy as np\n", 43 | "import soundfile as sf # for reading a soundfile\n", 44 | "from scipy import signal\n", 45 | "\n", 46 | "# use the system OS sound device for convenience\n", 47 | "import sounddevice as sd # for playback\n", 48 | "# sd.default.device = 0 # set default device -> not used, know what you're doing\n", 49 | "sd.query_devices() # check if desired default device is set" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "## Multi-dimensional continuous Systems\n", 57 | "\n", 58 | "In the first part of this exercise, we had a quick review on linear systems in one dimension, namely the time $t$. Now, we add three additional dimension $\\mathbf x = [x,y,z]^T$ in order to cover the three-dimensional space. Generally, the input signal $q \\in \\mathbb{C}$ and the corresponding output signal $p \\in \\mathbb{C}$ of a system $\\mathcal H$ are related via:\n", 59 | "\n", 60 | "$$p(\\mathbf x, t) = p(x,y,z,t) = \\mathcal{H}\\{q(\\mathbf x,t)\\} = \\mathcal{H}\\{q(x,y,z,t)\\}\\,.$$" 61 | ] 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "metadata": {}, 66 | "source": [ 67 | "### Linear Time-Space-Invariant (LTSI) Systems\n", 68 | "\n", 69 | "As simple as it sounds, LTI-system are linear, time-invariant and **space-invariant**. We had already covered the first two properties in the [last exercise](linear_systems_I.ipynb#Linear-Time-Invariant-%28LTI%29-Systems)." 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "#### Space-Invariance\n", 77 | "\n", 78 | "*Exercise*: Explain the term \"space-invariance\" in your own words.\n", 79 | "\n", 80 | "\n", 81 | "*Exercise*: What does this mean mathematically?\n", 82 | "\n", 83 | "$$\\mathcal{H}\\{q(\\mathbf x - \\mathbf x_0, t)\\} = \\,???\\, \\text{ for all } \\mathbf x_0 \\in \\mathbb{R}$$\n", 84 | "\n", 85 | "if $p(\\mathbf x, t) = \\mathcal{H} \\{ q(\\mathbf x,t) \\}$ is known.\n", 86 | "\n", 87 | "*Exercise*: Can you give an example, for which environment the space invariance does **not** hold?" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "### The Impulse Response\n", 95 | "\n", 96 | "#### ... of an LTSI System\n", 97 | "\n", 98 | "The impulse response $h(\\mathbf x, t)$ of an LTSI system characterises it completely. It is the system's response to a spatio-temporal Dirac impulse $\\delta(\\mathbf x)\\delta(t)$\n", 99 | "\n", 100 | "$$h(\\mathbf x, t) = h(x, y, z, t) = \\mathcal{H}\\{\\delta(\\mathbf x)\\delta(t)\\} = \\mathcal{H}\\{\\delta(x)\\delta(y)\\delta(z)\\delta(t)\\}\\,.$$\n", 101 | "\n", 102 | "We can describe the output signal $p(\\mathbf x, t)$ by a 4-dimensional convolution of the corresponding input signal $q(\\mathbf x, t)$ and the impulse response $h(\\mathbf x, t)$: \n", 103 | "\n", 104 | "$$ p(\\mathbf x, t) = \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} q(x_0, y_0, z_0, t_0)\\,h(x - x_0, y - y_0, z - z_0, t - t_0)\\,\\mathrm d x_0 \\mathrm d y_0 \\mathrm d z_0 \\mathrm d t_0$$\n", 105 | "\n", 106 | "Its short version reads\n", 107 | "\n", 108 | "$$ p(\\mathbf x, t) = q(\\mathbf x, t) *_{\\mathbf x, t} h(\\mathbf x, t) $$\n", 109 | "\n", 110 | "where $*$ is a common notation of the convolution.\n", 111 | "\n", 112 | "#### ... of an shift-variant LTI System\n", 113 | "\n", 114 | "For a shift-variant system, the impulse response is not independent from the shift in space:\n", 115 | "\n", 116 | "$$h(\\mathbf x, \\mathbf x_0, t) = \\mathcal{H}\\{\\delta(\\mathbf x - \\mathbf x_0)\\delta(t)\\}$$\n", 117 | "\n", 118 | "We can no longer describe the resulting output signal as a convolution with respect to the three dimensions in space:\n", 119 | "\n", 120 | "$$ p(\\mathbf x, t) = \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} q(x_0, y_0, z_0, t_0)\\,h(x,x_0,y,y_0,z,z_0,t-t_0)\\,\\mathrm d x_0\\,\\mathrm d y_0\\,\\mathrm d z_0\\,\\mathrm d t_0$$\n", 121 | "\n", 122 | "The integral with respect to time is still a linear convolution, though.\n" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "### The Spatial Frequency Response\n", 130 | "\n", 131 | "For an LTSI-System, the spatial transfer characteristics in terms of its frequency response is defined as the three-dimensional Fourier transform with respect to each dimension in space:\n", 132 | "\n", 133 | "$$ \\tilde h(\\mathbf k, t) = \\tilde h(k_x, k_y, k_z, t) = \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} h(x,y,z,t) e^{+jk_x x} e^{+jk_y y} e^{+jk_z z} \\,\\mathrm d x\\,\\mathrm d y\\,\\mathrm d z$$\n", 134 | "\n", 135 | "with the wave vector $\\mathbf k = [k_x, k_y, k_z]^T$. The Fourier transform with respect to one dimension is equivalent to the temporal Fourier transform with e.g. $\\omega \\rightarrow -k_x$.\n", 136 | "\n", 137 | "### The Spatio-Temporal Frequency Response\n", 138 | "\n", 139 | "Applying the temporal Fourier transform to $\\tilde h(\\mathbf k, t)$ yields the **Spatio-Temporal Frequency Response** of an LTSI-System\n", 140 | "\n", 141 | "$$ \\tilde H(\\mathbf k, \\omega) = \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} \\int_{-\\infty}^{\\infty} h(x,y,z,t) e^{+jk_x x} e^{+jk_y y} e^{+jk_z z} e^{-j\\omega t} \\,\\mathrm d x\\,\\mathrm d y\\,\\mathrm d z\\,\\mathrm d t$$\n", 142 | "\n", 143 | "## Sound Propagation as a Linear System\n", 144 | "\n", 145 | "Remember the inhomogeneous wave equation?\n", 146 | "\n", 147 | "$$\\Delta p(\\mathbf x, t) - \\frac{1}{c^2} \\frac{\\partial^2}{\\partial t^2} p(\\mathbf x, t) = -q(\\mathbf x, t)$$\n", 148 | "\n", 149 | "Its solution $p(\\mathbf x, t)$ has to incorporate boundary conditions and the inhomogenity $q(\\mathbf x, t)$. It describes sound propagation and can be interpreted as an LTI-system with the source density $q(\\mathbf x, t)$ as the input signal and the sound pressure $p(\\mathbf x, t)$ as the output signal. Remember the Green's function? It corresponds to the special inhomogenity $q(\\mathbf x, t) = \\delta(\\mathbf x - \\mathbf x_0)\\delta(t-t_0)$ and fulfils the boundary conditions. The inhomogeneous wave equation for the Green's functions reads\n", 150 | "\n", 151 | "$$\\Delta g(\\mathbf x | \\mathbf x_0 , t) - \\frac{1}{c^2} \\frac{\\partial^2}{\\partial t^2} g(\\mathbf x | \\mathbf x_0 , t) = - \\delta(\\mathbf x - \\mathbf x_0)\\delta(t)\\,.$$\n", 152 | "\n", 153 | "*Exercise*: What is the impulse response $h(\\mathbf x,\\mathbf x_0,t)$ of the wave equation as a linear system?\n", 154 | "\n", 155 | "Under free-field conditions, i.e. no boundary conditions, the wave equation becomes an LTSI-System with free-field Green's function $g_0(\\mathbf x , t) = \\delta(t - |\\mathbf x|/c)$ as its spatio-temporal impulse responses. In rooms, the impulse response is often subsumed under the term **Room Impulse Response**.\n", 156 | "\n", 157 | "### Measuring a Room Impulse Response\n", 158 | "\n", 159 | "#### A Very Simplistic Procedure\n", 160 | "\n", 161 | "There are several possibilities to excite the room with a signal being close to a Dirac - bursting balloons, gunshots, electrical spark discharges, ...; for simplicity (and for safety reasons), one could use two wooden boards which are clapped together. The signal recorded by a microphone standing at the certain positions $\\mathbf x$ then yields the approximate room impulse response $h(\\mathbf x,\\mathbf x_0,t)$\n", 162 | "\n", 163 | "![Two Wooden Boards](data/wooden_boards.jpg)\n", 164 | "\n", 165 | "That's not ideal - ideally all frequencies should be excited equally - but it shall be sufficient for demonstration purposes. As we will get to know later, that there are *much better* ways to measure room impulse responses!" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": 2, 171 | "metadata": { 172 | "execution": { 173 | "iopub.execute_input": "2020-11-26T13:10:06.496070Z", 174 | "iopub.status.busy": "2020-11-26T13:10:06.495576Z", 175 | "iopub.status.idle": "2020-11-26T13:10:13.101530Z", 176 | "shell.execute_reply": "2020-11-26T13:10:13.101930Z" 177 | } 178 | }, 179 | "outputs": [], 180 | "source": [ 181 | "# speech signal\n", 182 | "speech, fs = sf.read(\"data/xmas.wav\")\n", 183 | "# sloppy measured room impulse response\n", 184 | "# (measured with clap seen above, do not harm your ears!)\n", 185 | "rir, fs_rir = sf.read(\"data/rir_clap.wav\")\n", 186 | "\n", 187 | "assert fs == fs_rir # compare sample rates of signal and rir\n", 188 | "speech_clap = signal.fftconvolve(speech, rir) # convolve with respect to time\n", 189 | "# normalize to the same maximum value as the original speech signal:\n", 190 | "speech_clap = tools.normalize(speech_clap, np.max(np.abs(speech)))\n", 191 | "\n", 192 | "sd.play(speech, fs, blocking=True)\n", 193 | "sd.play(speech_clap, fs, blocking=True)" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "#### More elaborated procedures\n", 201 | "\n", 202 | "In slightly more modern *sweep method*, room is excited with a sine sweep, which is reproduced by means of a loudspeaker. The actual impulse response will be calculated from the excitation signal and the signal recorded by the microphone. Let's listen to some sweep signals (**watch the volume**!):" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": 3, 208 | "metadata": { 209 | "execution": { 210 | "iopub.execute_input": "2020-11-26T13:10:13.105844Z", 211 | "iopub.status.busy": "2020-11-26T13:10:13.105398Z", 212 | "iopub.status.idle": "2020-11-26T13:10:19.392534Z", 213 | "shell.execute_reply": "2020-11-26T13:10:19.392948Z" 214 | } 215 | }, 216 | "outputs": [], 217 | "source": [ 218 | "fstart = 65 # start with 65 Hertz, be nice to your onboard loudspeakers\n", 219 | "fstop = 15000 # stop a 15000 Hertz\n", 220 | "tlen = 3 # length of sweep signal\n", 221 | "t = np.arange(0, tlen*fs)/fs # samples\n", 222 | "\n", 223 | "sweep_lin = signal.chirp(t, fstart, tlen, fstop, method='linear')\n", 224 | "sweep_log = signal.chirp(t, fstart, tlen, fstop, method='logarithmic')\n", 225 | "\n", 226 | "sd.play(sweep_lin, fs, blocking=True)\n", 227 | "sd.play(sweep_log, fs, blocking=True)" 228 | ] 229 | }, 230 | { 231 | "cell_type": "markdown", 232 | "metadata": {}, 233 | "source": [ 234 | "## Fourier Analysis of a Sound Field\n", 235 | "\n", 236 | "### Spatial-Temporal Fourier Transform of a Plane Wave\n", 237 | "\n", 238 | "We already got to know the temporal spectrum of a [plane wave](physics_of_sound_I-solutions.ipynb#Plane-Wave) with a direction of propagation $\\mathbf n_{\\mathrm {pw}}$ which reads\n", 239 | "\n", 240 | "$$P_{pw}(\\mathbf x, \\omega) = \\mathrm{exp}\\left(-j\\frac{\\omega}{c} \\mathbf n_{\\mathrm {pw}} \\cdot \\mathbf x \\right) = \\mathrm{exp}\\left(-j\\frac{\\omega}{c} (n_{\\mathrm {pw},x} \\cdot x + n_{\\mathrm {pw},y} \\cdot y + n_{\\mathrm {pw},z} \\cdot z) \\right) \\,. $$\n", 241 | "\n", 242 | "*Exercise*: Calculate the three-dimensional, spatial Fourier Transform $\\tilde P_{pw}(\\mathbf k, \\omega)$ of the Plane Wave. Use the integral identity:\n", 243 | "\n", 244 | "$$ \\int_{-\\infty}^{\\infty} e^{+j a b}\\,\\mathrm d b\n", 245 | " = \\int_{-\\infty}^{\\infty} \\mathrm{exp}\\left(+j a b\\right)\\,\\mathrm d b \n", 246 | " = 2\\pi \\delta(a) \n", 247 | "$$" 248 | ] 249 | }, 250 | { 251 | "cell_type": "markdown", 252 | "metadata": { 253 | "collapsed": true 254 | }, 255 | "source": [ 256 | " " 257 | ] 258 | }, 259 | { 260 | "cell_type": "markdown", 261 | "metadata": {}, 262 | "source": [ 263 | "### Fourier Analysis using a Plane Wave Decomposition\n", 264 | "\n", 265 | "We now know that the value of $\\tilde P(\\mathbf k, \\omega)$ at $\\mathbf k = \\frac{\\omega}{c} \\mathbf n_{\\mathrm {pw}}$ corresponds to the amplitude of the plane wave propagation in the directions of $\\mathbf n_{\\mathrm {pw}}$. This can be straightforwardly used to compute the so-called plane wave composition:\n", 266 | "\n", 267 | "$$\n", 268 | "\\bar P(\\mathbf n_{\\mathrm {pw}}) = \\tilde P(\\mathbf k, \\omega)|_{\\mathbf k = \\frac{\\omega}{c} \\mathbf n_{\\mathrm {pw}}}\n", 269 | "$$\n", 270 | "\n", 271 | "After all this math, u can lay back and have a look at the demonstration, how to compute a plane wave decompositon out of two microphone signals.\n", 272 | "\n" 273 | ] 274 | }, 275 | { 276 | "cell_type": "markdown", 277 | "metadata": {}, 278 | "source": [ 279 | "## Solutions\n", 280 | "\n", 281 | "If you had problems solving some of the exercises, don't despair!\n", 282 | "Have a look at the [example solutions](linear_systems_II-solutions.ipynb)." 283 | ] 284 | }, 285 | { 286 | "cell_type": "markdown", 287 | "metadata": {}, 288 | "source": [ 289 | "

\n", 290 | " \n", 292 | " \"CC0\"\n", 293 | " \n", 294 | "
\n", 295 | " To the extent possible under law,\n", 296 | " the person who associated CC0\n", 297 | " with this work has waived all copyright and related or neighboring\n", 298 | " rights to this work.\n", 299 | "

" 300 | ] 301 | } 302 | ], 303 | "metadata": { 304 | "kernelspec": { 305 | "display_name": "mystiasp", 306 | "language": "python", 307 | "name": "mystiasp" 308 | }, 309 | "language_info": { 310 | "codemirror_mode": { 311 | "name": "ipython", 312 | "version": 3 313 | }, 314 | "file_extension": ".py", 315 | "mimetype": "text/x-python", 316 | "name": "python", 317 | "nbconvert_exporter": "python", 318 | "pygments_lexer": "ipython3", 319 | "version": "3.7.8" 320 | } 321 | }, 322 | "nbformat": 4, 323 | "nbformat_minor": 1 324 | } 325 | -------------------------------------------------------------------------------- /plot_direction.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Plot the live microphone signal(s) with matplotlib.""" 3 | import argparse 4 | from queue import Queue, Empty 5 | 6 | 7 | def int_or_str(text): 8 | """Helper function for argument parsing.""" 9 | try: 10 | return int(text) 11 | except ValueError: 12 | return text 13 | 14 | 15 | parser = argparse.ArgumentParser(description=__doc__) 16 | parser.add_argument( 17 | '-l', '--list-devices', action='store_true', 18 | help='show list of audio devices and exit') 19 | parser.add_argument( 20 | '-d', '--device', type=int_or_str, 21 | help='input device (numeric ID or substring)') 22 | parser.add_argument( 23 | '-w', '--window', type=float, default=200, metavar='DURATION', 24 | help='visible time slot (default: %(default)s ms)') 25 | parser.add_argument( 26 | '-i', '--interval', type=float, default=30, 27 | help='minimum time between plot updates (default: %(default)s ms)') 28 | parser.add_argument( 29 | '-b', '--blocksize', type=int, help='block size (in samples)') 30 | parser.add_argument( 31 | '-r', '--samplerate', type=float, help='sampling rate of audio device') 32 | parser.add_argument( 33 | '-n', '--downsample', type=int, default=1, metavar='N', 34 | help='display every Nth sample (default: %(default)s)') 35 | parser.add_argument( 36 | 'channels', type=int, default=[1,2], nargs='*', metavar='CHANNEL', 37 | help='input channels for estimation of direction (default: the first two)') 38 | parser.add_argument( 39 | '-D', '--distance', type=float, default=0.2, metavar='DISTANCE', 40 | help='distance of the two microphones channels (default: $(default) m)') 41 | args = parser.parse_args() 42 | if any(c < 1 for c in args.channels): 43 | parser.error('argument CHANNEL: must be >= 1') 44 | mapping = [c - 1 for c in args.channels] # Channel numbers start with 1 45 | queue = Queue() 46 | 47 | 48 | def audio_callback(indata, frames, time, status): 49 | """This is called (from a separate thread) for each audio block.""" 50 | if status: 51 | print(status, flush=True) 52 | # Fancy indexing with mapping creates a (necessary!) copy: 53 | queue.put(indata[::args.downsample, mapping]) 54 | 55 | 56 | def update_plot(frame): 57 | """This is called by matplotlib for each plot update. 58 | 59 | Typically, audio callbacks happen more frequently than plot updates, 60 | therefore the queue tends to contain multiple blocks of audio data. 61 | 62 | """ 63 | global newdata 64 | global corr 65 | block = True # The first read from the queue is blocking ... 66 | 67 | while True: 68 | try: 69 | data = queue.get(block=block) 70 | except Empty: 71 | break 72 | shift = len(data) 73 | 74 | newdata = np.roll(newdata, -shift, axis=0) 75 | newdata[-shift:,:] = data 76 | block=False # ... all further reads are non-blocking 77 | 78 | corr = np.correlate(newdata[:,0], newdata[:,1], mode='full') 79 | corr = np.abs(corr)/np.max(np.max(corr)) 80 | 81 | lines[0].set_ydata(corr) 82 | 83 | return lines 84 | 85 | try: 86 | from matplotlib.animation import FuncAnimation 87 | import matplotlib.pyplot as plt 88 | import numpy as np 89 | import sounddevice as sd 90 | 91 | if args.list_devices: 92 | print(sd.query_devices()) 93 | parser.exit() 94 | if args.samplerate is None: 95 | device_info = sd.query_devices(args.device, 'input') 96 | args.samplerate = device_info['default_samplerate'] 97 | 98 | length = int(args.window * args.samplerate/ (1000 * args.downsample)) 99 | ran = args.distance/343*args.samplerate/args.downsample 100 | newdata = np.zeros((length, len(args.channels))) 101 | corr = np.zeros((2*length-1)) 102 | 103 | phi = np.linspace(0, 180, 6); 104 | phi = np.append(phi, 90) 105 | xtics = ran*np.cos(phi*np.pi/180) +length-1 106 | xlabels = phi 107 | 108 | fig, ax = plt.subplots() 109 | lines = ax.plot(corr) 110 | ax.axis((-ran+length-1, ran+length-1, 0, 1)) 111 | ax.set_xticks(xtics) 112 | ax.set_xticklabels(xlabels) 113 | ax.yaxis.grid(True) 114 | fig.tight_layout(pad=0) 115 | 116 | stream = sd.InputStream( 117 | device=args.device, channels=max(args.channels), 118 | samplerate=args.samplerate, callback=audio_callback) 119 | ani = FuncAnimation(fig, update_plot, interval=args.interval, blit=True) 120 | with stream: 121 | plt.show() 122 | except Exception as e: 123 | parser.exit(type(e).__name__ + ': ' + str(e)) 124 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | jupyter 2 | notebook 3 | ipykernel 4 | matplotlib==3.1 5 | numpy 6 | scipy 7 | sfs==0.5.0 8 | sounddevice 9 | soundfile 10 | -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | """Some tools used in the communication acoustics exercises.""" 2 | from __future__ import division # Only needed for Python 2.x 3 | import numpy as np 4 | import os 5 | from scipy import signal 6 | try: 7 | from urllib.request import Request, urlopen # Python 3.x 8 | except ImportError: 9 | from urllib2 import Request, urlopen # Python 2.x 10 | 11 | 12 | def normalize(x, maximum=1, axis=None, out=None): 13 | """Normalize a signal to the given maximum (absolute) value. 14 | 15 | Parameters 16 | ---------- 17 | x : array_like 18 | Input signal. 19 | maximum : float or sequence of floats, optional 20 | Desired (absolute) maximum value. By default, the signal is 21 | normalized to +-1.0. If a sequence is given, it must have the 22 | same length as the dimension given by `axis`. Each sub-array 23 | along the given axis is normalized with one of the values. 24 | axis : int, optional 25 | Normalize along a given axis. 26 | By default, the flattened array is normalized. 27 | out : numpy.ndarray or similar, optional 28 | If given, the result is stored in `out` and `out` is returned. 29 | If `out` points to the same memory as `x`, the normalization 30 | happens in-place. 31 | 32 | Returns 33 | ------- 34 | numpy.ndarray 35 | The normalized signal. 36 | 37 | """ 38 | if axis is None and not np.isscalar(maximum): 39 | raise TypeError("If axis is not specified, maximum must be a scalar") 40 | 41 | maximum = np.max(np.abs(x), axis=axis) / maximum 42 | if axis is not None: 43 | maximum = np.expand_dims(maximum, axis=axis) 44 | return np.true_divide(x, maximum, out) 45 | 46 | 47 | def fade(x, in_length, out_length=None, type='l', copy=True): 48 | """Apply fade in/out to a signal. 49 | 50 | If `x` is two-dimenstional, this works along the columns (= first 51 | axis). 52 | 53 | This is based on the *fade* effect of SoX, see: 54 | http://sox.sourceforge.net/sox.html 55 | 56 | The C implementation can be found here: 57 | http://sourceforge.net/p/sox/code/ci/master/tree/src/fade.c 58 | 59 | Parameters 60 | ---------- 61 | x : array_like 62 | Input signal. 63 | in_length : int 64 | Length of fade-in in samples (contrary to SoX, where this is 65 | specified in seconds). 66 | out_length : int, optional 67 | Length of fade-out in samples. If not specified, `fade_in` is 68 | used also for the fade-out. 69 | type : {'t', 'q', 'h', 'l', 'p'}, optional 70 | Select the shape of the fade curve: 'q' for quarter of a sine 71 | wave, 'h' for half a sine wave, 't' for linear ("triangular") 72 | slope, 'l' for logarithmic, and 'p' for inverted parabola. 73 | The default is logarithmic. 74 | copy : bool, optional 75 | If `False`, the fade is applied in-place and a reference to 76 | `x` is returned. 77 | 78 | """ 79 | x = np.array(x, copy=copy) 80 | 81 | if out_length is None: 82 | out_length = in_length 83 | 84 | def make_fade(length, type): 85 | fade = np.arange(length) / length 86 | if type == 't': # triangle 87 | pass 88 | elif type == 'q': # quarter of sinewave 89 | fade = np.sin(fade * np.pi / 2) 90 | elif type == 'h': # half of sinewave... eh cosine wave 91 | fade = (1 - np.cos(fade * np.pi)) / 2 92 | elif type == 'l': # logarithmic 93 | fade = np.power(0.1, (1 - fade) * 5) # 5 means 100 db attenuation 94 | elif type == 'p': # inverted parabola 95 | fade = (1 - (1 - fade)**2) 96 | else: 97 | raise ValueError("Unknown fade type {0!r}".format(type)) 98 | return fade 99 | 100 | # Using .T w/o [:] causes error: https://github.com/numpy/numpy/issues/2667 101 | x[:in_length].T[:] *= make_fade(in_length, type) 102 | x[len(x) - out_length:].T[:] *= make_fade(out_length, type)[::-1] 103 | return x 104 | 105 | 106 | def db(x, power=False): 107 | """Convert a signal to decibel. 108 | 109 | Parameters 110 | ---------- 111 | x : array_like 112 | Input signal. Values of 0 lead to negative infinity. 113 | power : bool, optional 114 | If `power=False` (the default), `x` is squared before 115 | conversion. 116 | 117 | """ 118 | with np.errstate(divide='ignore'): 119 | return 10 if power else 20 * np.log10(np.abs(x)) 120 | 121 | 122 | def blackbox(x, samplerate, axis=0): 123 | """Some unknown (except that it's LTI) digital system. 124 | 125 | Parameters 126 | ---------- 127 | x : array_like 128 | Input signal. 129 | samplerate : float 130 | Sampling rate in Hertz. 131 | axis : int, optional 132 | The axis of the input data array along which to apply the 133 | system. By default, this is the first axis. 134 | 135 | Returns 136 | ------- 137 | numpy.ndarray 138 | The output signal. 139 | 140 | """ 141 | # You are not supposed to look! 142 | b, a = signal.cheby1(8, 0.1, 3400 * 2 / samplerate) 143 | x = signal.lfilter(b, a, x, axis) 144 | b, a = signal.cheby1(4, 0.1, 300 * 2 / samplerate, 'high') 145 | return signal.lfilter(b, a, x, axis) 146 | 147 | 148 | def blackbox_nonlinear(x, samplerate, axis=0): 149 | """Some unknown (except that it's non-linear) digital system. 150 | 151 | See Also 152 | -------- 153 | blackbox 154 | 155 | """ 156 | # You are not supposed to look! 157 | thr = 1/7 158 | out = blackbox(x, samplerate, axis) 159 | x = np.max(np.abs(out)) * thr 160 | return np.clip(out, -x, x, out=out) 161 | 162 | 163 | class HttpFile(object): 164 | """based on http://stackoverflow.com/a/7852229/500098""" 165 | 166 | def __init__(self, url): 167 | self._url = url 168 | self._offset = 0 169 | self._content_length = None 170 | 171 | def __len__(self): 172 | if self._content_length is None: 173 | response = urlopen(self._url) 174 | self._content_length = int(response.headers["Content-length"]) 175 | return self._content_length 176 | 177 | def read(self, size=-1): 178 | request = Request(self._url) 179 | if size < 0: 180 | end = len(self) - 1 181 | else: 182 | end = self._offset + size - 1 183 | request.add_header('Range', "bytes={0}-{1}".format(self._offset, end)) 184 | data = urlopen(request).read() 185 | self._offset += len(data) 186 | return data 187 | 188 | def seek(self, offset, whence=os.SEEK_SET): 189 | if whence == os.SEEK_SET: 190 | self._offset = offset 191 | elif whence == os.SEEK_CUR: 192 | self._offset += offset 193 | elif whence == os.SEEK_END: 194 | self._offset = len(self) + offset 195 | else: 196 | raise ValueError("Invalid whence") 197 | 198 | def tell(self): 199 | return self._offset 200 | --------------------------------------------------------------------------------