├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── .gitlab-ci.yml ├── .readthedocs.yml ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── conf.py ├── index.rst ├── module.rst ├── references.rst └── refs.bib ├── rir_generator ├── __init__.py └── _rir │ ├── build.py │ ├── rir_generator_core.cpp │ └── rir_generator_core.h ├── setup.cfg ├── setup.py ├── tests └── test_example.py └── tox.ini /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["3.8", "3.9", "3.10"] 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v3 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip wheel 30 | python -m pip install -e .[tests,docs] 31 | - name: Test with pytest 32 | run: | 33 | pytest 34 | - name: Build Docs and Package 35 | run: | 36 | python setup.py build_sphinx 37 | python setup.py egg_info -b.dev sdist --formats gztar 38 | python setup.py bdist_wheel 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #####=== Python ===##### 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.c 10 | *.o 11 | *.so 12 | *.h.gch 13 | 14 | # Distribution / packaging 15 | .Python 16 | env/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *,cover 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | 59 | # Sphinx documentation 60 | docs/_build/ 61 | 62 | # PyBuilder 63 | target/ 64 | rirgenerator.c 65 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | .job_template: &job_definition # Hidden key that defines an anchor named 'job_definition' 2 | stage: tests 3 | before_script: 4 | - echo "deb http://deb.debian.org/debian jessie main" >> /etc/apt/sources.list 5 | - echo "deb-src http://deb.debian.org/debian jessie main" >> /etc/apt/sources.list 6 | - apt-get update -yy 7 | - apt-get build-dep -yy python-numpy 8 | - apt-get install -yy libsndfile1 libsndfile1-dev 9 | - pip install -e .[tests,docs] 10 | script: 11 | - py.test 12 | 13 | stages: 14 | - tests 15 | 16 | test:3.6: 17 | image: python:3.6 18 | <<: *job_definition 19 | 20 | test:3.7: 21 | image: python:3.7 22 | <<: *job_definition 23 | 24 | test:3.8: 25 | image: python:3.8 26 | <<: *job_definition 27 | 28 | test:latest: 29 | image: python:latest 30 | <<: *job_definition 31 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | python: 2 | pip_install: true 3 | extra_requirements: 4 | - tests 5 | - docs 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | cache: pip 3 | python: 4 | - 3.6 5 | - 3.7 6 | - 3.8 7 | before_install: 8 | - sudo apt-get update -qq 9 | - sudo apt-get install python3-numpy python3-scipy 10 | - sudo apt-get install -qq libatlas-dev libatlas-base-dev liblapack-dev gfortran 11 | install: 12 | - pip install -U pip wheel 13 | - pip install -e .[tests,docs] 14 | script: 15 | - py.test 16 | - python setup.py build_sphinx 17 | - python setup.py egg_info -b.dev sdist --formats gztar 18 | - python setup.py bdist_wheel 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nils Werner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include rir_generator/_rir *.cpp *.h *.py 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Room Impulse Response Generator 2 | 3 | [![Documentation Status](https://readthedocs.org/projects/rir-generator/badge/?version=latest)](https://rir-generator.readthedocs.io/en/latest/?badge=latest) 4 | [![Build Status](https://travis-ci.org/audiolabs/rir-generator.svg?branch=master)](https://travis-ci.org/audiolabs/rir-generator) 5 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.4133077.svg)](https://doi.org/10.5281/zenodo.4133077) 6 | 7 | Python- and C-based [room impulse response](https://en.wikipedia.org/wiki/Impulse_response#Acoustic_and_audio_applications) generator, for use in [convolutional reverb](https://en.wikipedia.org/wiki/Convolution_reverb). 8 | 9 | Official Python port of https://github.com/ehabets/RIR-Generator. 10 | 11 | ## Installation 12 | 13 | ```sh 14 | pip install rir-generator 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```python 20 | import numpy as np 21 | import scipy.signal as ss 22 | import soundfile as sf 23 | import rir_generator as rir 24 | 25 | signal, fs = sf.read("bark.wav", always_2d=True) 26 | 27 | h = rir.generate( 28 | c=340, # Sound velocity (m/s) 29 | fs=fs, # Sample frequency (samples/s) 30 | r=[ # Receiver position(s) [x y z] (m) 31 | [2, 1.5, 1], 32 | [2, 1.5, 2], 33 | [2, 1.5, 3] 34 | ], 35 | s=[2, 3.5, 2], # Source position [x y z] (m) 36 | L=[5, 4, 6], # Room dimensions [x y z] (m) 37 | reverberation_time=0.4, # Reverberation time (s) 38 | nsample=4096, # Number of output samples 39 | ) 40 | 41 | print(h.shape) # (4096, 3) 42 | print(signal.shape) # (11462, 2) 43 | 44 | # Convolve 2-channel signal with 3 impulse responses 45 | signal = ss.convolve(h[:, None, :], signal[:, :, None]) 46 | 47 | print(signal.shape) # (15557, 2, 3) 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # rir_generator documentation build configuration file, created by 5 | # sphinx-quickstart on Sun Apr 20 14:28:17 2014. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | import sphinx_rtd_theme 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | # sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | "sphinx.ext.autodoc", 35 | "sphinx.ext.imgmath", 36 | "sphinx.ext.napoleon", 37 | "sphinx.ext.autosummary", 38 | "sphinxcontrib.bibtex", 39 | "numpydoc", 40 | ] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ["_templates"] 44 | 45 | # The suffix of source filenames. 46 | source_suffix = ".rst" 47 | 48 | # The encoding of source files. 49 | # source_encoding = 'utf-8-sig' 50 | 51 | # The master toctree document. 52 | master_doc = "index" 53 | 54 | # General information about the project. 55 | project = "rir_generator" 56 | copyright = "2020, Nils Werner" 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | version = "0.1.0" 64 | # The full version, including alpha/beta/rc tags. 65 | release = "0.1.0" 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # language = None 70 | 71 | # There are two options for replacing |today|: either, you set today to some 72 | # non-false value, then it is used: 73 | # today = '' 74 | # Else, today_fmt is used as the format for a strftime call. 75 | # today_fmt = '%B %d, %Y' 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | exclude_patterns = ["_build", "**tests**", "**setup**", "**extern**", "**data**"] 80 | 81 | # The reST default role (used for this markup: `text`) to use for all 82 | # documents. 83 | # default_role = None 84 | 85 | # If true, '()' will be appended to :func: etc. cross-reference text. 86 | # add_function_parentheses = True 87 | 88 | # If true, the current module name will be prepended to all description 89 | # unit titles (such as .. function::). 90 | # add_module_names = True 91 | 92 | # If true, sectionauthor and moduleauthor directives will be shown in the 93 | # output. They are ignored by default. 94 | # show_authors = False 95 | 96 | # The name of the Pygments (syntax highlighting) style to use. 97 | pygments_style = "sphinx" 98 | 99 | # A list of ignored prefixes for module index sorting. 100 | # modindex_common_prefix = [] 101 | 102 | # If true, keep warnings as "system message" paragraphs in the built documents. 103 | # keep_warnings = False 104 | 105 | 106 | # -- Options for HTML output ---------------------------------------------- 107 | 108 | # The theme to use for HTML and HTML Help pages. See the documentation for 109 | # a list of builtin themes. 110 | html_theme = "sphinx_rtd_theme" 111 | 112 | # Theme options are theme-specific and customize the look and feel of a theme 113 | # further. For a list of options available for each theme, see the 114 | # documentation. 115 | # html_theme_options = {} 116 | 117 | # Add any paths that contain custom themes here, relative to this directory. 118 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 119 | 120 | # The name for this set of Sphinx documents. If None, it defaults to 121 | # " v documentation". 122 | # html_title = None 123 | 124 | # A shorter title for the navigation bar. Default is the same as html_title. 125 | # html_short_title = None 126 | 127 | # The name of an image file (relative to this directory) to place at the top 128 | # of the sidebar. 129 | # html_logo = None 130 | 131 | # The name of an image file (within the static path) to use as favicon of the 132 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 133 | # pixels large. 134 | # html_favicon = None 135 | 136 | # Add any paths that contain custom static files (such as style sheets) here, 137 | # relative to this directory. They are copied after the builtin static files, 138 | # so a file named "default.css" will overwrite the builtin "default.css". 139 | html_static_path = ["_static"] 140 | 141 | # Add any extra paths that contain custom files (such as robots.txt or 142 | # .htaccess) here, relative to this directory. These files are copied 143 | # directly to the root of the documentation. 144 | # html_extra_path = [] 145 | 146 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 147 | # using the given strftime format. 148 | # html_last_updated_fmt = '%b %d, %Y' 149 | 150 | # If true, SmartyPants will be used to convert quotes and dashes to 151 | # typographically correct entities. 152 | # html_use_smartypants = True 153 | 154 | # Custom sidebar templates, maps document names to template names. 155 | # html_sidebars = {} 156 | 157 | # Additional templates that should be rendered to pages, maps page names to 158 | # template names. 159 | # html_additional_pages = {} 160 | 161 | # If false, no module index is generated. 162 | # html_domain_indices = True 163 | 164 | # If false, no index is generated. 165 | # html_use_index = True 166 | 167 | # If true, the index is split into individual pages for each letter. 168 | # html_split_index = False 169 | 170 | # If true, links to the reST sources are added to the pages. 171 | # html_show_sourcelink = True 172 | 173 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 174 | # html_show_sphinx = True 175 | 176 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 177 | # html_show_copyright = True 178 | 179 | # If true, an OpenSearch description file will be output, and all pages will 180 | # contain a tag referring to it. The value of this option must be the 181 | # base URL from which the finished HTML is served. 182 | # html_use_opensearch = '' 183 | 184 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 185 | # html_file_suffix = None 186 | 187 | # Output file base name for HTML help builder. 188 | htmlhelp_basename = "dspydoc" 189 | 190 | 191 | # -- Options for LaTeX output --------------------------------------------- 192 | 193 | latex_elements = { 194 | # The paper size ('letterpaper' or 'a4paper'). 195 | # 'papersize': 'letterpaper', 196 | # The font size ('10pt', '11pt' or '12pt'). 197 | # 'pointsize': '10pt', 198 | # Additional stuff for the LaTeX preamble. 199 | # 'preamble': '', 200 | } 201 | 202 | # Grouping the document tree into LaTeX files. List of tuples 203 | # (source start file, target name, title, 204 | # author, documentclass [howto, manual, or own class]). 205 | latex_documents = [ 206 | ( 207 | "index", 208 | "rir_generator.tex", 209 | "rir_generator Documentation", 210 | "Nils Werner", 211 | "manual", 212 | ), 213 | ] 214 | 215 | # The name of an image file (relative to this directory) to place at the top of 216 | # the title page. 217 | # latex_logo = None 218 | 219 | # For "manual" documents, if this is true, then toplevel headings are parts, 220 | # not chapters. 221 | # latex_use_parts = False 222 | 223 | # If true, show page references after internal links. 224 | # latex_show_pagerefs = False 225 | 226 | # If true, show URL addresses after external links. 227 | # latex_show_urls = False 228 | 229 | # Documents to append as an appendix to all manuals. 230 | # latex_appendices = [] 231 | 232 | # If false, no module index is generated. 233 | # latex_domain_indices = True 234 | 235 | 236 | # -- Options for manual page output --------------------------------------- 237 | 238 | # One entry per manual page. List of tuples 239 | # (source start file, name, description, authors, manual section). 240 | man_pages = [ 241 | ("index", "rir_generator", "rir_generator Documentation", ["Nils Werner"], 1) 242 | ] 243 | 244 | # If true, show URL addresses after external links. 245 | # man_show_urls = False 246 | 247 | 248 | # -- Options for Texinfo output ------------------------------------------- 249 | 250 | # Grouping the document tree into Texinfo files. List of tuples 251 | # (source start file, target name, title, author, 252 | # dir menu entry, description, category) 253 | texinfo_documents = [ 254 | ( 255 | "index", 256 | "rir_generator", 257 | "rir_generator Documentation", 258 | "Nils Werner", 259 | "rir_generator", 260 | "One line description of project.", 261 | "Miscellaneous", 262 | ), 263 | ] 264 | 265 | # Documents to append as an appendix to all manuals. 266 | # texinfo_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | # texinfo_domain_indices = True 270 | 271 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 272 | # texinfo_show_urls = 'footnote' 273 | 274 | # If true, do not generate a @detailmenu in the "Top" node's menu. 275 | # texinfo_no_detailmenu = False 276 | bibtex_bibfiles = ["refs.bib"] 277 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Room Impulse Response Generator 2 | =============================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :hidden: 7 | 8 | module 9 | references 10 | 11 | .. image:: https://readthedocs.org/projects/rir-generator/badge/?version=latest 12 | :target: https://rir-generator.readthedocs.io/en/latest/?badge=latest 13 | .. image:: https://travis-ci.org/audiolabs/rir-generator.svg?branch=master 14 | :target: https://travis-ci.org/audiolabs/rir-generator 15 | .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4133077.svg 16 | :target: https://doi.org/10.5281/zenodo.4133077 17 | 18 | Python- and C-based `room impulse response`_ generator, for use in `convolutional reverb`_. 19 | 20 | Official Python port of https://github.com/ehabets/RIR-Generator. 21 | 22 | Example 23 | ------- 24 | 25 | .. code-block:: python 26 | 27 | import numpy as np 28 | import scipy.signal as ss 29 | import soundfile as sf 30 | import rir_generator as rir 31 | 32 | signal, fs = sf.read("bark.wav", always_2d=True) 33 | 34 | h = rir.generate( 35 | c=340, # Sound velocity (m/s) 36 | fs=fs, # Sample frequency (samples/s) 37 | r=[ # Receiver position(s) [x y z] (m) 38 | [2, 1.5, 1], 39 | [2, 1.5, 2], 40 | [2, 1.5, 3] 41 | ], 42 | s=[2, 3.5, 2], # Source position [x y z] (m) 43 | L=[5, 4, 6], # Room dimensions [x y z] (m) 44 | reverberation_time=0.4, # Reverberation time (s) 45 | nsample=4096, # Number of output samples 46 | ) 47 | 48 | print(h.shape) # (4096, 3) 49 | print(signal.shape) # (11462, 2) 50 | 51 | # Convolve 2-channel signal with 3 impulse responses 52 | signal = ss.convolve(h[:, None, :], signal[:, :, None]) 53 | 54 | print(signal.shape) # (15557, 2, 3) 55 | 56 | .. _room impulse response: https://en.wikipedia.org/wiki/Impulse_response#Acoustic_and_audio_applications 57 | .. _convolutional reverb: https://en.wikipedia.org/wiki/Convolution_reverb 58 | 59 | 60 | Indices and tables 61 | ================== 62 | 63 | * :ref:`genindex` 64 | * :ref:`modindex` 65 | * :ref:`search` 66 | -------------------------------------------------------------------------------- /docs/module.rst: -------------------------------------------------------------------------------- 1 | rir_generator module 2 | ==================== 3 | 4 | Module reference 5 | ---------------- 6 | 7 | .. automodule:: rir_generator 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | -------------------------------------------------------------------------------- /docs/references.rst: -------------------------------------------------------------------------------- 1 | References 2 | ========== 3 | 4 | .. bibliography:: refs.bib 5 | :all: 6 | -------------------------------------------------------------------------------- /docs/refs.bib: -------------------------------------------------------------------------------- 1 | @misc{rir_generator, 2 | author = {Emanuël Habets}, 3 | title = {ehabets/RIR-Generator: RIR Generator}, 4 | month = oct, 5 | year = 2020, 6 | publisher = {Zenodo}, 7 | version = {v2.1.20141124}, 8 | doi = {10.5281/zenodo.4096349}, 9 | url = {https://github.com/ehabets/RIR-Generator} 10 | } 11 | 12 | @article{image_method, 13 | author = {Alien, J. B. and Berkley, D. A.}, 14 | title = {Image method for efficiently simulating small‐room acoustics}, 15 | journal = {The Journal of the Acoustical Society of America}, 16 | volume = {60}, 17 | number = {S1}, 18 | pages = {S9-S9}, 19 | year = {1976}, 20 | doi = {10.1121/1.2003643}, 21 | } 22 | 23 | @article{simulating, 24 | author = {Peterson,Patrick M. }, 25 | title = {Simulating the response of multiple microphones to a single acoustic source in a reverberant room}, 26 | journal = {The Journal of the Acoustical Society of America}, 27 | volume = {80}, 28 | number = {5}, 29 | pages = {1527-1529}, 30 | year = {1986}, 31 | doi = {10.1121/1.394357}, 32 | } 33 | -------------------------------------------------------------------------------- /rir_generator/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | import numpy as np 3 | from enum import Enum 4 | from . import rir 5 | 6 | 7 | class mtype(Enum): 8 | """Microphone type.""" 9 | 10 | bidirectional = b"b" 11 | b = b"b" 12 | cardioid = b"c" 13 | c = b"c" 14 | subcardioid = b"s" 15 | s = b"s" 16 | hypercardioid = b"h" 17 | h = b"h" 18 | omnidirectional = b"o" 19 | o = b"o" 20 | 21 | 22 | def generate( 23 | c, 24 | fs, 25 | r, 26 | s, 27 | L, 28 | beta=None, 29 | reverberation_time=None, 30 | nsample=None, 31 | mtype=mtype.omnidirectional, 32 | order=-1, 33 | dim=3, 34 | orientation=None, 35 | hp_filter=True, 36 | ): 37 | """Generate room impulse response. 38 | 39 | Parameters 40 | ---------- 41 | c : float 42 | Sound velocity in m/s. Usually between 340 and 350. 43 | fs : float 44 | Sampling frequency in Hz. 45 | r : array_like 46 | 1D or 2D array of floats, specifying the :code:`(x, y, z)` coordinates of the receiver(s) 47 | in m. Must be of shape :code:`(3,)` or :code:`(x, 3)` where :code:`x` 48 | is the number of receivers. 49 | s : array_like 50 | 1D array of floats specifying the :code:`(x, y, z)` coordinates of the source in m. 51 | L : array_like 52 | 1D array of floats specifying the room dimensions :code:`(x, y, z)` in m. 53 | beta : array_like, optional 54 | 1D array of floats specifying the reflection coefficients 55 | 56 | .. code-block:: 57 | 58 | [beta_x1, beta_x2, beta_y1, beta_y2, beta_z1, beta_z2] 59 | 60 | or 61 | 62 | .. code-block:: 63 | 64 | [(beta_x1, beta_x2), (beta_y1, beta_y2), (beta_z1, beta_z2)] 65 | 66 | Must be of shape :code:`(6,)` or :code:`(3, 2)`. 67 | 68 | You must define **exactly one** of :attr:`beta` or 69 | :attr:`reverberation_time`. 70 | reverberation_time : float, optional 71 | Reverberation time (T_60) in seconds. 72 | 73 | You must define **exactly one** of :attr:`beta` or 74 | :attr:`reverberation_time`. 75 | nsample : int, optional 76 | number of samples to calculate, default is :code:`T_60 * fs`. 77 | mtype : mtype, optional 78 | Microphone type, one of :class:`mtype`. 79 | Defaults to :class:`mtype.omnidirectional`. 80 | order : int, optional 81 | Reflection order, default is :code:`-1`, i.e. maximum order. 82 | dim : int, optional 83 | Room dimension (:code:`2` or :code:`3`), default is :code:`3`. 84 | orientation : array_like, optional 85 | 1D array direction in which the microphones are pointed, specified 86 | using azimuth and elevation angles (in radians), default is 87 | :code:`[0, 0]`. 88 | hp_filter : boolean, optional 89 | Enable high-pass filter, the high-pass filter is enabled by default. 90 | 91 | Returns 92 | ------- 93 | h : array_like 94 | The room impulse response, shaped `(nsample, len(r))` 95 | 96 | Example 97 | ------- 98 | 99 | >>> import rir_generator 100 | >>> h = rir_generator.generate( 101 | ... c=340, 102 | ... fs=16000, 103 | ... r=[ 104 | ... [2, 1.5, 2], 105 | ... [2, 1.5, 3] 106 | ... ], 107 | ... s=[2, 3.5, 2], 108 | ... L=[5, 4, 6], 109 | ... reverberation_time=0.4, 110 | ... nsample=4096, 111 | ... mtype=rir_generator.mtype.omnidirectional, 112 | ... ) 113 | 114 | 115 | """ 116 | r = np.atleast_2d(np.asarray(r, dtype=np.double)).T.copy() 117 | assert r.shape[0] == 3 118 | 119 | L = np.asarray(L, dtype=np.double) 120 | assert L.shape == (3,) 121 | 122 | s = np.asarray(s, dtype=np.double) 123 | assert s.shape == (3,) 124 | 125 | if beta is not None: 126 | beta = np.asarray(beta, dtype=np.double) 127 | assert beta.shape == (6,) or beta.shape == (3, 2) 128 | beta = beta.reshape(3, 2) 129 | 130 | if (r > L[:, None]).any() or (r < 0).any(): 131 | raise ValueError("r is outside the room") 132 | 133 | if (s > L).any() or (s < 0).any(): 134 | raise ValueError("s is outside the room") 135 | 136 | # Make sure orientation is a 2-element array, even if passed a single value 137 | if orientation is None: 138 | orientation = np.zeros(2, dtype=np.double) 139 | orientation = np.atleast_1d(np.asarray(orientation, dtype=np.double)) 140 | if orientation.shape == (1,): 141 | orientation = np.pad(orientation, (0, 1), "constant") 142 | assert orientation.shape == (2,) 143 | 144 | assert order >= -1 145 | assert dim in (2, 3) 146 | 147 | # Volume of room 148 | V = np.prod(L) 149 | # Surface area of walls 150 | A = L[::-1] * np.roll(L[::-1], 1) 151 | 152 | if beta is not None: 153 | alpha = np.sum(np.sum(1 - beta**2, axis=1) * np.sum(A)) 154 | 155 | reverberation_time = max( 156 | 24 * np.log(10.0) * V / (c * alpha), 157 | 0.128, 158 | ) 159 | 160 | elif reverberation_time is not None: 161 | if reverberation_time != 0: 162 | S = 2 * np.sum(A) 163 | 164 | alpha = 24 * np.log(10.0) * V / (c * S * reverberation_time) 165 | 166 | if alpha > 1: 167 | raise ValueError( 168 | "Error: The reflection coefficients cannot be " 169 | "calculated using the current room parameters, " 170 | "i.e. room size and reverberation time. Please " 171 | "specify the reflection coefficients or change the " 172 | "room parameters." 173 | ) 174 | 175 | beta = np.full((3, 2), fill_value=np.sqrt(1 - alpha), dtype=np.double) 176 | else: 177 | beta = np.zeros((3, 2), dtype=np.double) 178 | else: 179 | raise ValueError( 180 | "Error: Specify either RT60 (ex: reverberation_time=0.4) or " 181 | "reflection coefficients (beta=[0.3,0.2,0.5,0.1,0.1,0.1])" 182 | ) 183 | 184 | if nsample is None: 185 | nsample = int(reverberation_time * fs) 186 | 187 | if dim == 2: 188 | beta[-1, :] = 0 189 | 190 | numMics = r.shape[1] 191 | 192 | imp = np.zeros((nsample, numMics), dtype=np.double) 193 | 194 | p_imp = rir.ffi.cast("double*", rir.ffi.from_buffer(imp)) 195 | p_r = rir.ffi.cast("double*", rir.ffi.from_buffer(r)) 196 | p_s = rir.ffi.cast("double*", rir.ffi.from_buffer(s)) 197 | p_L = rir.ffi.cast("double*", rir.ffi.from_buffer(L)) 198 | p_beta = rir.ffi.cast("double*", rir.ffi.from_buffer(beta)) 199 | p_orientation = rir.ffi.cast("double*", rir.ffi.from_buffer(orientation)) 200 | 201 | rir.lib.computeRIR( 202 | p_imp, 203 | float(c), 204 | float(fs), 205 | p_r, 206 | numMics, 207 | nsample, 208 | p_s, 209 | p_L, 210 | p_beta, 211 | mtype.value, 212 | order, 213 | p_orientation, 214 | 1 if hp_filter else 0, 215 | ) 216 | return imp 217 | -------------------------------------------------------------------------------- /rir_generator/_rir/build.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from cffi import FFI 4 | 5 | rir_generator = FFI() 6 | 7 | compile_extras = { 8 | "libraries": ["m"], 9 | } 10 | 11 | # MSVC does not like -march=native -lm 12 | if sys.platform == "win32": 13 | compile_extras = {} 14 | 15 | rir_generator.set_source( 16 | "rir_generator.rir", 17 | '#include "rir_generator_core.h"', 18 | sources=["rir_generator/_rir/rir_generator_core.cpp"], 19 | include_dirs=["rir_generator/_rir"], 20 | **compile_extras 21 | ) 22 | 23 | rir_generator.cdef( 24 | """ 25 | void computeRIR( 26 | double* imp, 27 | double c, 28 | double fs, 29 | double* rr, 30 | int nr_of_mics, 31 | int nsamples, 32 | double* ss, 33 | double* LL, 34 | double* beta, 35 | char mtype, 36 | int order, 37 | double* angle, 38 | int hp_filter 39 | ); 40 | """ 41 | ) 42 | 43 | if __name__ == "__main__": 44 | rir_generator.compile() 45 | -------------------------------------------------------------------------------- /rir_generator/_rir/rir_generator_core.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Program : Room Impulse Response Generator 3 | 4 | Description : Computes the response of an acoustic source to one or more 5 | microphones in a reverberant room using the image method [1,2]. 6 | 7 | [1] J.B. Allen and D.A. Berkley, 8 | Image method for efficiently simulating small-room acoustics, 9 | Journal Acoustic Society of America, 65(4), April 1979, p 943. 10 | 11 | [2] P.M. Peterson, 12 | Simulating the response of multiple microphones to a single 13 | acoustic source in a reverberant room, Journal Acoustic 14 | Society of America, 80(5), November 1986. 15 | 16 | Author : dr.ir. E.A.P. Habets (e.habets@ieee.org) 17 | 18 | Version : 2.2.20201022 19 | 20 | History : 1.0.20030606 Initial version 21 | 1.1.20040803 + Microphone directivity 22 | + Improved phase accuracy [2] 23 | 1.2.20040312 + Reflection order 24 | 1.3.20050930 + Reverberation Time 25 | 1.4.20051114 + Supports multi-channels 26 | 1.5.20051116 + High-pass filter [1] 27 | + Microphone directivity control 28 | 1.6.20060327 + Minor improvements 29 | 1.7.20060531 + Minor improvements 30 | 1.8.20080713 + Minor improvements 31 | 1.9.20090822 + 3D microphone directivity control 32 | 2.0.20100920 + Calculation of the source-image position 33 | changed in the code and tutorial. 34 | This ensures a proper response to reflections 35 | in case a directional microphone is used. 36 | 2.1.20120318 + Avoid the use of unallocated memory 37 | 2.1.20140721 + Fixed computation of alpha 38 | 2.1.20141124 + The window and sinc are now both centered 39 | around t=0 40 | 2.2.20201022 + Fixed arrival time 41 | 42 | MIT License 43 | 44 | Copyright (C) 2003-2020 E.A.P. Habets 45 | 46 | Permission is hereby granted, free of charge, to any person obtaining a copy 47 | of this software and associated documentation files (the "Software"), to deal 48 | in the Software without restriction, including without limitation the rights 49 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 50 | copies of the Software, and to permit persons to whom the Software is 51 | furnished to do so, subject to the following conditions: 52 | 53 | The above copyright notice and this permission notice shall be included in all 54 | copies or substantial portions of the Software. 55 | 56 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 57 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 58 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 59 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 60 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 61 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 62 | SOFTWARE. 63 | */ 64 | 65 | #define _USE_MATH_DEFINES 66 | 67 | #include 68 | #include 69 | #include "math.h" 70 | #include "rir_generator_core.h" 71 | 72 | #define ROUND(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5)) 73 | 74 | #ifndef M_PI 75 | #define M_PI 3.14159265358979323846 76 | #endif 77 | 78 | double sinc(double x) 79 | { 80 | if (x == 0) 81 | return(1.); 82 | else 83 | return(sin(x)/x); 84 | } 85 | 86 | double sim_microphone(double x, double y, double z, double* microphone_angle, char mtype) 87 | { 88 | if (mtype=='b' || mtype=='c' || mtype=='s' || mtype=='h') 89 | { 90 | double gain, vartheta, varphi, rho; 91 | 92 | // Polar Pattern rho 93 | // --------------------------- 94 | // Bidirectional 0 95 | // Hypercardioid 0.25 96 | // Cardioid 0.5 97 | // Subcardioid 0.75 98 | // Omnidirectional 1 99 | 100 | switch(mtype) 101 | { 102 | case 'b': 103 | rho = 0; 104 | break; 105 | case 'h': 106 | rho = 0.25; 107 | break; 108 | case 'c': 109 | rho = 0.5; 110 | break; 111 | case 's': 112 | rho = 0.75; 113 | break; 114 | default: 115 | rho = 1; 116 | }; 117 | 118 | vartheta = acos(z/sqrt(pow(x,2)+pow(y,2)+pow(z,2))); 119 | varphi = atan2(y,x); 120 | 121 | gain = sin(M_PI/2-microphone_angle[1]) * sin(vartheta) * cos(microphone_angle[0]-varphi) + cos(M_PI/2-microphone_angle[1]) * cos(vartheta); 122 | gain = rho + (1-rho) * gain; 123 | 124 | return gain; 125 | } 126 | else 127 | { 128 | return 1; 129 | } 130 | } 131 | 132 | void computeRIR(double* imp, double c, double fs, double* rr, int nMicrophones, int nSamples, double* ss, double* LL, double* beta, char microphone_type, int nOrder, double* microphone_angle, int isHighPassFilter){ 133 | 134 | // Temporary variables and constants (high-pass filter) 135 | const double W = 2*M_PI*100/fs; // The cut-off frequency equals 100 Hz 136 | const double R1 = exp(-W); 137 | const double B1 = 2*R1*cos(W); 138 | const double B2 = -R1 * R1; 139 | const double A1 = -(1+R1); 140 | double X0; 141 | double Y[3]; 142 | 143 | // Temporary variables and constants (image-method) 144 | const double Fc = 0.5; // The normalized cut-off frequency equals (fs/2) / fs = 0.5 145 | const int Tw = 2 * ROUND(0.004*fs); // The width of the low-pass FIR equals 8 ms 146 | const double cTs = c/fs; 147 | double* LPI = new double[Tw]; 148 | double r[3]; 149 | double s[3]; 150 | double L[3]; 151 | double Rm[3]; 152 | double Rp_plus_Rm[3]; 153 | double refl[3]; 154 | double fdist,dist; 155 | double gain; 156 | int startPosition; 157 | int n1, n2, n3; 158 | int q, j, k; 159 | int mx, my, mz; 160 | int n; 161 | 162 | s[0] = ss[0]/cTs; s[1] = ss[1]/cTs; s[2] = ss[2]/cTs; 163 | L[0] = LL[0]/cTs; L[1] = LL[1]/cTs; L[2] = LL[2]/cTs; 164 | 165 | for (int idxMicrophone = 0; idxMicrophone < nMicrophones ; idxMicrophone++) 166 | { 167 | // [x_1 x_2 ... x_N y_1 y_2 ... y_N z_1 z_2 ... z_N] 168 | r[0] = rr[idxMicrophone + 0*nMicrophones] / cTs; 169 | r[1] = rr[idxMicrophone + 1*nMicrophones] / cTs; 170 | r[2] = rr[idxMicrophone + 2*nMicrophones] / cTs; 171 | 172 | n1 = (int) ceil(nSamples/(2*L[0])); 173 | n2 = (int) ceil(nSamples/(2*L[1])); 174 | n3 = (int) ceil(nSamples/(2*L[2])); 175 | 176 | // Generate room impulse response 177 | for (mx = -n1 ; mx <= n1 ; mx++) 178 | { 179 | Rm[0] = 2*mx*L[0]; 180 | 181 | for (my = -n2 ; my <= n2 ; my++) 182 | { 183 | Rm[1] = 2*my*L[1]; 184 | 185 | for (mz = -n3 ; mz <= n3 ; mz++) 186 | { 187 | Rm[2] = 2*mz*L[2]; 188 | 189 | for (q = 0 ; q <= 1 ; q++) 190 | { 191 | Rp_plus_Rm[0] = (1-2*q)*s[0] - r[0] + Rm[0]; 192 | refl[0] = pow(beta[0], abs(mx-q)) * pow(beta[1], abs(mx)); 193 | 194 | for (j = 0 ; j <= 1 ; j++) 195 | { 196 | Rp_plus_Rm[1] = (1-2*j)*s[1] - r[1] + Rm[1]; 197 | refl[1] = pow(beta[2], abs(my-j)) * pow(beta[3], abs(my)); 198 | 199 | for (k = 0 ; k <= 1 ; k++) 200 | { 201 | Rp_plus_Rm[2] = (1-2*k)*s[2] - r[2] + Rm[2]; 202 | refl[2] = pow(beta[4],abs(mz-k)) * pow(beta[5], abs(mz)); 203 | 204 | dist = sqrt(pow(Rp_plus_Rm[0], 2) + pow(Rp_plus_Rm[1], 2) + pow(Rp_plus_Rm[2], 2)); 205 | 206 | if (abs(2*mx-q)+abs(2*my-j)+abs(2*mz-k) <= nOrder || nOrder == -1) 207 | { 208 | fdist = floor(dist); 209 | if (fdist < nSamples) 210 | { 211 | gain = sim_microphone(Rp_plus_Rm[0], Rp_plus_Rm[1], Rp_plus_Rm[2], microphone_angle, microphone_type) 212 | * refl[0]*refl[1]*refl[2]/(4*M_PI*dist*cTs); 213 | 214 | for (n = 0 ; n < Tw ; n++) 215 | { 216 | const double t = (n-0.5*Tw+1) - (dist-fdist); 217 | LPI[n] = 0.5 * (1.0 + cos(2.0*M_PI*t/Tw)) * 2.0*Fc * sinc(M_PI*2.0*Fc*t); 218 | } 219 | startPosition = (int) fdist-(Tw/2)+1; 220 | for (n = 0 ; n < Tw; n++) 221 | if (startPosition+n >= 0 && startPosition+n < nSamples) 222 | imp[idxMicrophone + nMicrophones*(startPosition+n)] += gain * LPI[n]; 223 | } 224 | } 225 | } 226 | } 227 | } 228 | } 229 | } 230 | } 231 | 232 | // 'Original' high-pass filter as proposed by Allen and Berkley. 233 | if (isHighPassFilter == 1) 234 | { 235 | for (int idx = 0 ; idx < 3 ; idx++) {Y[idx] = 0;} 236 | for (int idx = 0 ; idx < nSamples ; idx++) 237 | { 238 | X0 = imp[idxMicrophone+nMicrophones*idx]; 239 | Y[2] = Y[1]; 240 | Y[1] = Y[0]; 241 | Y[0] = B1*Y[1] + B2*Y[2] + X0; 242 | imp[idxMicrophone+nMicrophones*idx] = Y[0] + A1*Y[1] + R1*Y[2]; 243 | } 244 | } 245 | } 246 | 247 | delete[] LPI; 248 | } 249 | -------------------------------------------------------------------------------- /rir_generator/_rir/rir_generator_core.h: -------------------------------------------------------------------------------- 1 | #ifndef RIR_GENERATOR_CORE_H 2 | #define RIR_GENERATOR_CORE_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | void computeRIR(double* imp, double c, double fs, double* rr, int nMicrophones, int nSamples, double* ss, double* LL, double* beta, char microphone_type, int nOrder, double* microphone_angle, int isHighPassFilter); 11 | 12 | #ifdef __cplusplus 13 | } 14 | #endif 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | addopts = --ignore scribble.py --doctest-modules --cov-report term-missing --cov rir_generator --black 3 | 4 | [metadata] 5 | long_description = file: README.md 6 | long_description_content_type = text/markdown 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import setuptools 4 | 5 | 6 | if __name__ == "__main__": 7 | setuptools.setup( 8 | name="rir-generator", 9 | version="0.2.0", 10 | description="Room Impulse Response Generator.", 11 | author="Nils Werner", 12 | author_email="nils.werner@fau.de", 13 | license="MIT", 14 | packages=setuptools.find_packages(), 15 | setup_requires=["cffi>=1.1.0"], 16 | install_requires=[ 17 | "numpy>=1.6", 18 | "scipy>=0.13.0", 19 | "cffi>=1.1.0", 20 | ], 21 | cffi_modules=[ 22 | "rir_generator/_rir/build.py:rir_generator", 23 | ], 24 | extras_require={ 25 | "tests": [ 26 | "pytest", 27 | "pytest-cov", 28 | "pytest-black", 29 | "sphinx", 30 | "sphinxcontrib-napoleon", 31 | "sphinx_rtd_theme", 32 | "numpydoc", 33 | ], 34 | "docs": [ 35 | "sphinx", 36 | "sphinxcontrib-napoleon", 37 | "sphinxcontrib-bibtex", 38 | "sphinx_rtd_theme", 39 | "numpydoc", 40 | ], 41 | }, 42 | zip_safe=False, 43 | ) 44 | -------------------------------------------------------------------------------- /tests/test_example.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | import rir_generator 4 | 5 | 6 | @pytest.fixture(params=[16000, 44100.5]) 7 | def fs(request): 8 | return request.param 9 | 10 | 11 | @pytest.fixture( 12 | params=[ 13 | (1, [2, 1.5, 2]), 14 | (1, [[2, 1.5, 2]]), 15 | (2, [[2, 1.5, 2], [1, 1.5, 2]]), 16 | ] 17 | ) 18 | def nMics_r(request): 19 | return request.param 20 | 21 | 22 | @pytest.fixture 23 | def nMics(nMics_r): 24 | return nMics_r[0] 25 | 26 | 27 | @pytest.fixture 28 | def r(nMics_r): 29 | return nMics_r[1] 30 | 31 | 32 | @pytest.fixture(params=[[2, 3.5, 2]]) 33 | def s(request): 34 | return request.param 35 | 36 | 37 | @pytest.fixture( 38 | params=[ 39 | [5, 4, 6], 40 | [5, 4.0, 6.1], 41 | ] 42 | ) 43 | def L(request): 44 | return request.param 45 | 46 | 47 | @pytest.fixture(params=[340, 343.1]) 48 | def c(request): 49 | return request.param 50 | 51 | 52 | @pytest.fixture(params=[2048, 4096]) 53 | def n(request): 54 | return request.param 55 | 56 | 57 | @pytest.fixture( 58 | params=[ 59 | (0.4, None), 60 | (None, [0.1, 0.1, 0.2, 0.2, 0.3, 0.3]), 61 | (None, [(0.1, 0.1), (0.2, 0.2), (0.3, 0.3)]), 62 | ] 63 | ) 64 | def reverberation_time_beta(request): 65 | return request.param 66 | 67 | 68 | @pytest.fixture 69 | def reverberation_time(reverberation_time_beta): 70 | return reverberation_time_beta[0] 71 | 72 | 73 | @pytest.fixture 74 | def beta(reverberation_time_beta): 75 | return reverberation_time_beta[1] 76 | 77 | 78 | @pytest.fixture( 79 | params=[ 80 | rir_generator.mtype.omnidirectional, 81 | rir_generator.mtype.o, 82 | rir_generator.mtype.hypercardioid, 83 | ] 84 | ) 85 | def mtype(request): 86 | return request.param 87 | 88 | 89 | @pytest.fixture(params=[2, -1]) 90 | def order(request): 91 | return request.param 92 | 93 | 94 | @pytest.fixture(params=[2, 3]) 95 | def dim(request): 96 | return request.param 97 | 98 | 99 | @pytest.fixture(params=[0, [np.pi / 2, 0]]) 100 | def orientation(request): 101 | return request.param 102 | 103 | 104 | @pytest.fixture(params=[True, False]) 105 | def hp_filter(request): 106 | return request.param 107 | 108 | 109 | def test_parameters( 110 | c, 111 | fs, 112 | r, 113 | nMics, 114 | s, 115 | L, 116 | n, 117 | reverberation_time, 118 | beta, 119 | mtype, 120 | order, 121 | dim, 122 | orientation, 123 | hp_filter, 124 | ): 125 | out = rir_generator.generate( 126 | c, 127 | fs, 128 | r, 129 | s, 130 | L, 131 | reverberation_time=reverberation_time, 132 | beta=beta, 133 | nsample=n, 134 | mtype=mtype, 135 | order=order, 136 | dim=dim, 137 | orientation=orientation, 138 | hp_filter=hp_filter, 139 | ) 140 | 141 | assert out.shape == (n, nMics) 142 | assert not np.all(np.isclose(out, 0)) 143 | 144 | 145 | def test_multiple_mics( 146 | c, fs, s, L, n, reverberation_time, beta, mtype, order, dim, orientation, hp_filter 147 | ): 148 | out1 = rir_generator.generate( 149 | c, 150 | fs, 151 | [2, 1.5, 2], 152 | s, 153 | L, 154 | reverberation_time=reverberation_time, 155 | beta=beta, 156 | nsample=n, 157 | mtype=mtype, 158 | order=order, 159 | dim=dim, 160 | orientation=orientation, 161 | hp_filter=hp_filter, 162 | ) 163 | 164 | out2 = rir_generator.generate( 165 | c, 166 | fs, 167 | [[2, 1.5, 2], [1, 1.5, 2]], 168 | s, 169 | L, 170 | reverberation_time=reverberation_time, 171 | beta=beta, 172 | nsample=n, 173 | mtype=mtype, 174 | order=order, 175 | dim=dim, 176 | orientation=orientation, 177 | hp_filter=hp_filter, 178 | ) 179 | 180 | assert np.allclose(out1[:, 0], out2[:, 0]) 181 | 182 | 183 | @pytest.mark.parametrize( 184 | "r, s", 185 | [ 186 | ([2, 3.5, 2], [2, 5.5, 2]), 187 | ([2, 5.5, 2], [2, 1.5, 2]), 188 | ([[2, 5.5, 2], [2, 3.5, 2]], [2, 1.5, 2]), 189 | ], 190 | ) 191 | def test_outside_room(r, s): 192 | with pytest.raises(ValueError): 193 | rir_generator.generate( 194 | 340, 195 | 16000, 196 | r, 197 | s, 198 | L=[5, 4, 6], 199 | reverberation_time=0.4, 200 | ) 201 | 202 | 203 | @pytest.mark.parametrize( 204 | "beta", 205 | [ 206 | [(0.1, 0.1, 0.2), (0.2, 0.3, 0.3)], 207 | ], 208 | ) 209 | def test_beta_shape(r, s, beta): 210 | print(beta) 211 | with pytest.raises(AssertionError): 212 | rir_generator.generate( 213 | 340, 214 | 16000, 215 | r, 216 | s, 217 | L=[5, 4, 6], 218 | beta=beta, 219 | ) 220 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py3 3 | 4 | [testenv] 5 | changedir={envtmpdir} 6 | extras= 7 | tests 8 | docs 9 | commands= 10 | py.test {toxinidir}/tests {posargs} 11 | --------------------------------------------------------------------------------