├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── README.md ├── conftest.py ├── docs ├── conf.py ├── index.rst ├── internal.rst ├── internal │ ├── mdct.fast.rst │ ├── mdct.fast.transforms.rst │ └── mdct.slow.transforms.rst ├── modules.rst └── modules │ ├── mdct.rst │ └── mdct.windows.rst ├── mdct ├── __init__.py ├── fast │ ├── __init__.py │ └── transforms.py ├── slow │ ├── __init__.py │ └── transforms.py └── windows.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests ├── test_all.py ├── test_issues.py └── test_windows.py └── tox.ini /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | workflows: 3 | version: 2 4 | test: 5 | jobs: 6 | - test-latest 7 | - test-3.6 8 | jobs: 9 | test-latest: &test-template 10 | docker: 11 | - image: circleci/python:latest 12 | working_directory: ~/repo 13 | steps: 14 | - checkout 15 | - restore_cache: 16 | keys: 17 | - v1-dependencies-{{ checksum "setup.py" }} 18 | - v1-dependencies- 19 | - run: 20 | name: install dependencies 21 | command: | 22 | echo "deb http://httpredir.debian.org/debian buster main non-free contrib" | sudo tee -a /etc/apt/sources.list 23 | echo "deb-src http://httpredir.debian.org/debian buster main non-free contrib" | sudo tee -a /etc/apt/sources.list 24 | sudo apt-get update -yy 25 | sudo apt-get build-dep -yy python-numpy fftw3 26 | sudo apt-get install -yy fftw3-dev 27 | python3 -m venv venv 28 | . venv/bin/activate 29 | pip install -e .[tests,docs] 30 | - save_cache: 31 | paths: 32 | - ./venv 33 | key: v1-dependencies-{{ checksum "setup.py" }} 34 | - run: 35 | name: run tests 36 | command: | 37 | . venv/bin/activate 38 | pytest 39 | test-3.6: 40 | <<: *test-template 41 | docker: 42 | - image: circleci/python:3.6 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: https://goel.io/joe 2 | 3 | #####=== Python ===##### 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | scribble.py 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 International Audio Laboratories Erlangen 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MDCT 2 | ==== 3 | 4 | [![Build Status](https://travis-ci.org/nils-werner/mdct.svg?branch=master)](https://travis-ci.org/nils-werner/mdct) 5 | [![Docs Status](https://readthedocs.org/projects/mdct/badge/?version=latest)](https://mdct.readthedocs.org/en/latest/) 6 | 7 | A fast MDCT implementation using SciPy and FFTs 8 | 9 | 10 | Installation 11 | ------------ 12 | 13 | As usual 14 | 15 | pip install mdct 16 | 17 | 18 | ## Dependencies 19 | 20 | - NumPy 21 | - SciPy 22 | - STFT 23 | 24 | 25 | Usage 26 | ----- 27 | 28 | 29 | import mdct 30 | 31 | spectrum = mdct.mdct(sig) 32 | 33 | 34 | **Also see the [docs](http://mdct.readthedocs.io/)** 35 | 36 | References 37 | ---------- 38 | 39 | - Implementation: Marina Bosi, Richard E. Goldberg and Leonardo Chiariglione, "Introduction to Digital Audio Coding and Standards", Kluwer Academic Publishers, 01 December, 2002. 40 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import scipy 3 | import pytest 4 | import mdct 5 | import mdct.slow 6 | import random as rand 7 | import functools 8 | 9 | 10 | @pytest.fixture 11 | def random(): 12 | rand.seed(0) 13 | numpy.random.seed(0) 14 | 15 | 16 | @pytest.fixture(params=(5, 6)) 17 | def length(request): 18 | return request.param 19 | 20 | 21 | @pytest.fixture 22 | def N(length): 23 | # must be multiple of 1024 b/c of stft 24 | return length * 1024 25 | 26 | 27 | @pytest.fixture 28 | def sig(N, random): 29 | return numpy.random.rand(N) 30 | 31 | 32 | @pytest.fixture(params=(256, 1024, 2048)) 33 | def framelength(request): 34 | return request.param 35 | 36 | 37 | @pytest.fixture 38 | def backsig(N, random, odd): 39 | if odd: 40 | return numpy.random.rand(N) 41 | else: 42 | return numpy.random.rand(N + 1) 43 | 44 | 45 | @pytest.fixture 46 | def spectrum(framelength, random, length, odd): 47 | if odd: 48 | return numpy.random.rand(framelength // 2, length * 2 + 1) 49 | else: 50 | return numpy.random.rand(framelength // 2 + 1, length * 2 + 1) 51 | 52 | 53 | @pytest.fixture(params=( 54 | mdct.fast, 55 | mdct.slow, 56 | )) 57 | def module(request): 58 | return request.param 59 | 60 | 61 | @pytest.fixture(params=(True, False)) 62 | def odd(request): 63 | return request.param 64 | 65 | 66 | @pytest.fixture(params=( 67 | scipy.signal.cosine, 68 | functools.partial(mdct.windows.kaiser_derived, beta=4.) 69 | )) 70 | def window(request): 71 | return request.param 72 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # mdct 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.pngmath', 36 | 'sphinx.ext.napoleon', 37 | 'sphinx.ext.autosummary', 38 | 'numpydoc' 39 | ] 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ['_templates'] 43 | 44 | # The suffix of source filenames. 45 | source_suffix = '.rst' 46 | 47 | # The encoding of source files. 48 | # source_encoding = 'utf-8-sig' 49 | 50 | # The master toctree document. 51 | master_doc = 'index' 52 | 53 | # General information about the project. 54 | project = 'MDCT' 55 | copyright = '2014, Nils Werner' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = '0.4' 63 | # The full version, including alpha/beta/rc tags. 64 | release = '0.4' 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | # language = None 69 | 70 | # There are two options for replacing |today|: either, you set today to some 71 | # non-false value, then it is used: 72 | # today = '' 73 | # Else, today_fmt is used as the format for a strftime call. 74 | # today_fmt = '%B %d, %Y' 75 | 76 | # List of patterns, relative to source directory, that match files and 77 | # directories to ignore when looking for source files. 78 | exclude_patterns = ['_build', '**tests**', '**setup**', '**extern**', 79 | '**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 | 197 | # The font size ('10pt', '11pt' or '12pt'). 198 | # 'pointsize': '10pt', 199 | 200 | # Additional stuff for the LaTeX preamble. 201 | # 'preamble': '', 202 | } 203 | 204 | # Grouping the document tree into LaTeX files. List of tuples 205 | # (source start file, target name, title, 206 | # author, documentclass [howto, manual, or own class]). 207 | latex_documents = [ 208 | ('index', 'index.tex', 'MDCT Documentation', 209 | 'Nils Werner', 'manual'), 210 | ] 211 | 212 | # The name of an image file (relative to this directory) to place at the top of 213 | # the title page. 214 | # latex_logo = None 215 | 216 | # For "manual" documents, if this is true, then toplevel headings are parts, 217 | # not chapters. 218 | # latex_use_parts = False 219 | 220 | # If true, show page references after internal links. 221 | # latex_show_pagerefs = False 222 | 223 | # If true, show URL addresses after external links. 224 | # latex_show_urls = False 225 | 226 | # Documents to append as an appendix to all manuals. 227 | # latex_appendices = [] 228 | 229 | # If false, no module index is generated. 230 | # latex_domain_indices = True 231 | 232 | 233 | # -- Options for manual page output --------------------------------------- 234 | 235 | # One entry per manual page. List of tuples 236 | # (source start file, name, description, authors, manual section). 237 | man_pages = [ 238 | ('index', 'mdct', 'MDCT Documentation', 239 | ['Nils Werner'], 1) 240 | ] 241 | 242 | # If true, show URL addresses after external links. 243 | # man_show_urls = False 244 | 245 | 246 | # -- Options for Texinfo output ------------------------------------------- 247 | 248 | # Grouping the document tree into Texinfo files. List of tuples 249 | # (source start file, target name, title, author, 250 | # dir menu entry, description, category) 251 | texinfo_documents = [ 252 | ('index', 'mdct', 'MDCT Documentation', 253 | 'Nils Werner', 'MDCT', 254 | 'One line description of project.', 255 | 'Miscellaneous'), 256 | ] 257 | 258 | # Documents to append as an appendix to all manuals. 259 | # texinfo_appendices = [] 260 | 261 | # If false, no module index is generated. 262 | # texinfo_domain_indices = True 263 | 264 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 265 | # texinfo_show_urls = 'footnote' 266 | 267 | # If true, do not generate a @detailmenu in the "Top" node's menu. 268 | # texinfo_no_detailmenu = False 269 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | MDCT 2 | ==== 3 | 4 | This toolkit implements several related transforms and their inverses: 5 | 6 | - **Modified Discrete Cosine Transform (MDCT)** 7 | - Modified Discrete Sine Transform (MDST) 8 | - Modulated Complex Lapped Transform (MCLT) aka Complex Modified Discrete Cosine Transform (CMDCT) 9 | 10 | All transforms are implemented as 11 | 12 | - the complete lapped transform, along with windowing and time domain aliasing cancellation (TDAC) reconstruction and 13 | - the core un-windowed standalone transform. 14 | 15 | All transforms are implemeted in 16 | 17 | - :py:mod:`mdct.fast`,a fast, FFT-based method (for actual use), see [Bosi] 18 | - :py:mod:`mdct.slow`, a slow, pure-Python fashion (for testing) and 19 | 20 | Usage 21 | ----- 22 | 23 | .. warning:: 24 | :py:mod:`mdct.fast` is exposed as :py:mod:`mdct`. Please use this module directly. 25 | 26 | .. code-block:: python 27 | 28 | import mdct 29 | spec = mdct.mdct(signal) 30 | output = mdct.imdct(spec) 31 | 32 | .. toctree:: 33 | :hidden: 34 | 35 | self 36 | modules 37 | internal 38 | 39 | Indices and tables 40 | ================== 41 | 42 | * :ref:`genindex` 43 | * :ref:`modindex` 44 | * :ref:`search` 45 | 46 | 47 | .. [Bosi] Marina Bosi, Richard E. Goldberg and Leonardo Chiariglione, 48 | "Introduction to Digital Audio Coding and Standards", Kluwer Academic Publishers, 01 December, 2002. 49 | -------------------------------------------------------------------------------- /docs/internal.rst: -------------------------------------------------------------------------------- 1 | Internal Modules 2 | ================ 3 | 4 | .. warning:: 5 | All necessary functions are exposed as the :py:mod:`mdct` module 6 | itself, please do not use internal modules directly. 7 | 8 | .. toctree:: 9 | 10 | internal/mdct.fast 11 | internal/mdct.fast.transforms 12 | internal/mdct.slow.transforms 13 | -------------------------------------------------------------------------------- /docs/internal/mdct.fast.rst: -------------------------------------------------------------------------------- 1 | mdct.fast module 2 | ================ 3 | 4 | .. automodule:: mdct.fast 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/internal/mdct.fast.transforms.rst: -------------------------------------------------------------------------------- 1 | mdct.fast.transforms module 2 | =========================== 3 | 4 | .. automodule:: mdct.fast.transforms 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/internal/mdct.slow.transforms.rst: -------------------------------------------------------------------------------- 1 | mdct.slow.transforms module 2 | =========================== 3 | 4 | .. automodule:: mdct.slow.transforms 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | Modules 2 | ======= 3 | 4 | .. toctree:: 5 | 6 | modules/mdct 7 | modules/mdct.windows 8 | -------------------------------------------------------------------------------- /docs/modules/mdct.rst: -------------------------------------------------------------------------------- 1 | mdct module 2 | =========== 3 | 4 | .. automodule:: mdct 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/modules/mdct.windows.rst: -------------------------------------------------------------------------------- 1 | mdct.windows module 2 | =================== 3 | 4 | .. automodule:: mdct.windows 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /mdct/__init__.py: -------------------------------------------------------------------------------- 1 | from . import windows 2 | from . import fast 3 | from .fast import cmdct, icmdct, mclt, imclt, mdct, imdct, mdst, imdst 4 | 5 | """ Module for calculating lapped MDCT 6 | 7 | .. note:: 8 | This module exposes all needed transforms. 9 | 10 | """ 11 | 12 | __all__ = [ 13 | 'mdct', 'imdct', 14 | 'mdst', 'imdst', 15 | 'cmdct', 'icmdct', 16 | 'mclt', 'imclt', 17 | ] 18 | -------------------------------------------------------------------------------- /mdct/fast/__init__.py: -------------------------------------------------------------------------------- 1 | """ Module for calculating lapped MDCT using FFT 2 | 3 | .. warning:: 4 | Functions defined in this module are exposed using the :py:mod:`mdct` 5 | module itself, please do not use this module directly. 6 | 7 | """ 8 | 9 | import functools 10 | 11 | import stft 12 | from . import transforms as transforms_default 13 | 14 | __all__ = [ 15 | 'mdct', 'imdct', 16 | 'mdst', 'imdst', 17 | 'cmdct', 'icmdct', 18 | 'mclt', 'imclt', 19 | ] 20 | 21 | 22 | def mdct( 23 | x, 24 | odd=True, 25 | transforms=None, 26 | **kwargs 27 | ): 28 | """ Calculate lapped MDCT of input signal 29 | 30 | Parameters 31 | ---------- 32 | x : array_like 33 | The signal to be transformed. May be a 1D vector for single channel or 34 | a 2D matrix for multi channel data. In case of a mono signal, the data 35 | is must be a 1D vector of length :code:`samples`. In case of a multi 36 | channel signal, the data must be in the shape of :code:`samples x 37 | channels`. 38 | odd : boolean, optional 39 | Switch to oddly stacked transform. Defaults to :code:`True`. 40 | framelength : int 41 | The signal frame length. Defaults to :code:`2048`. 42 | hopsize : int 43 | The signal frame hopsize. Defaults to :code:`None`. Setting this 44 | value will override :code:`overlap`. 45 | overlap : int 46 | The signal frame overlap coefficient. Value :code:`x` means 47 | :code:`1/x` overlap. Defaults to :code:`2`. Note that anything but 48 | :code:`2` will result in a filterbank without perfect reconstruction. 49 | centered : boolean 50 | Pad input signal so that the first and last window are centered around 51 | the beginning of the signal. Defaults to :code:`True`. 52 | Disabling this will result in aliasing 53 | in the first and last half-frame. 54 | window : callable, array_like 55 | Window to be used for deringing. Can be :code:`False` to disable 56 | windowing. Defaults to :code:`scipy.signal.cosine`. 57 | transforms : module, optional 58 | Module reference to core transforms. Mostly used to replace 59 | fast with slow core transforms, for testing. Defaults to 60 | :mod:`mdct.fast` 61 | padding : int 62 | Zero-pad signal with x times the number of samples. 63 | Defaults to :code:`0`. 64 | save_settings : boolean 65 | Save settings used here in attribute :code:`out.stft_settings` so that 66 | :func:`ispectrogram` can infer these settings without the developer 67 | having to pass them again. 68 | 69 | Returns 70 | ------- 71 | out : array_like 72 | The signal (or matrix of signals). In case of a mono output signal, the 73 | data is formatted as a 1D vector of length :code:`samples`. In case of 74 | a multi channel output signal, the data is formatted as :code:`samples 75 | x channels`. 76 | 77 | See Also 78 | -------- 79 | mdct.fast.transforms.mdct : MDCT 80 | 81 | """ 82 | if transforms is None: 83 | transforms = transforms_default 84 | 85 | kwargs.setdefault('framelength', 2048) 86 | 87 | if not odd: 88 | return stft.spectrogram( 89 | x, 90 | transform=[ 91 | functools.partial(transforms.mdct, odd=False), 92 | functools.partial(transforms.mdst, odd=False), 93 | ], 94 | halved=False, 95 | **kwargs 96 | ) 97 | else: 98 | return stft.spectrogram( 99 | x, 100 | transform=transforms.mdct, 101 | halved=False, 102 | **kwargs 103 | ) 104 | 105 | 106 | def imdct( 107 | X, 108 | odd=True, 109 | transforms=None, 110 | **kwargs 111 | ): 112 | """ Calculate lapped inverse MDCT of input signal 113 | 114 | Parameters 115 | ---------- 116 | x : array_like 117 | The spectrogram to be inverted. May be a 2D matrix for single channel 118 | or a 3D tensor for multi channel data. In case of a mono signal, the 119 | data must be in the shape of :code:`bins x frames`. In case of a multi 120 | channel signal, the data must be in the shape of :code:`bins x frames x 121 | channels`. 122 | odd : boolean, optional 123 | Switch to oddly stacked transform. Defaults to :code:`True`. 124 | framelength : int 125 | The signal frame length. Defaults to infer from data. 126 | hopsize : int 127 | The signal frame hopsize. Defaults to infer from data. Setting this 128 | value will override :code:`overlap`. 129 | overlap : int 130 | The signal frame overlap coefficient. Value :code:`x` means 131 | :code:`1/x` overlap. Defaults to infer from data. Note that anything 132 | but :code:`2` will result in a filterbank without perfect 133 | reconstruction. 134 | centered : boolean 135 | Pad input signal so that the first and last window are centered around 136 | the beginning of the signal. Defaults to to infer from data. 137 | The first and last half-frame will have aliasing, so using 138 | centering during forward MDCT is recommended. 139 | window : callable, array_like 140 | Window to be used for deringing. Can be :code:`False` to disable 141 | windowing. Defaults to to infer from data. 142 | halved : boolean 143 | Switch to reconstruct the other halve of the spectrum if the forward 144 | transform has been truncated. Defaults to to infer from data. 145 | transforms : module, optional 146 | Module reference to core transforms. Mostly used to replace 147 | fast with slow core transforms, for testing. Defaults to 148 | :mod:`mdct.fast` 149 | padding : int 150 | Zero-pad signal with x times the number of samples. Defaults to infer 151 | from data. 152 | outlength : int 153 | Crop output signal to length. Useful when input length of spectrogram 154 | did not fit into framelength and input data had to be padded. Not 155 | setting this value will disable cropping, the output data may be 156 | longer than expected. 157 | 158 | Returns 159 | ------- 160 | out : array_like 161 | The output signal 162 | 163 | See Also 164 | -------- 165 | mdct.fast.transforms.imdct : inverse MDCT 166 | 167 | """ 168 | if transforms is None: 169 | transforms = transforms_default 170 | 171 | kwargs.setdefault('framelength', 2048) 172 | 173 | if not odd: 174 | return stft.ispectrogram( 175 | X, 176 | transform=[ 177 | functools.partial(transforms.imdct, odd=False), 178 | functools.partial(transforms.imdst, odd=False), 179 | ], 180 | halved=False, 181 | **kwargs 182 | ) 183 | else: 184 | return stft.ispectrogram( 185 | X, 186 | transform=transforms.imdct, 187 | halved=False, 188 | **kwargs 189 | ) 190 | 191 | 192 | def mdst( 193 | x, 194 | odd=True, 195 | transforms=None, 196 | **kwargs 197 | ): 198 | """ Calculate lapped MDST of input signal 199 | 200 | Parameters 201 | ---------- 202 | x : array_like 203 | The input signal 204 | odd : boolean, optional 205 | Switch to oddly stacked transform. Defaults to :code:`True`. 206 | transforms : module, optional 207 | Module reference to core transforms. Mostly used to replace 208 | fast with slow core transforms, for testing. Defaults to 209 | :mod:`mdct.fast` 210 | Additional keyword arguments passed to :code:`stft.spectrogram` 211 | 212 | Returns 213 | ------- 214 | out : array_like 215 | The output signal 216 | 217 | See Also 218 | -------- 219 | mdct.fast.transforms.mdst : MDST 220 | 221 | """ 222 | if transforms is None: 223 | transforms = transforms_default 224 | 225 | kwargs.setdefault('framelength', 2048) 226 | 227 | if not odd: 228 | return stft.spectrogram( 229 | x, 230 | transform=[ 231 | functools.partial(transforms.mdst, odd=False), 232 | functools.partial(transforms.mdct, odd=False), 233 | ], 234 | halved=False, 235 | **kwargs 236 | ) 237 | else: 238 | return stft.spectrogram( 239 | x, 240 | transform=transforms.mdst, 241 | halved=False, 242 | **kwargs 243 | ) 244 | 245 | 246 | def imdst( 247 | X, 248 | odd=True, 249 | transforms=None, 250 | **kwargs 251 | ): 252 | """ Calculate lapped inverse MDST of input signal 253 | 254 | Parameters 255 | ---------- 256 | x : array_like 257 | The input signal 258 | odd : boolean, optional 259 | Switch to oddly stacked transform. Defaults to :code:`True`. 260 | transforms : module, optional 261 | Module reference to core transforms. Mostly used to replace 262 | fast with slow core transforms, for testing. Defaults to 263 | :mod:`mdct.fast` 264 | Additional keyword arguments passed to :code:`stft.spectrogram` 265 | 266 | Returns 267 | ------- 268 | out : array_like 269 | The output signal 270 | 271 | See Also 272 | -------- 273 | mdct.fast.transforms.imdst : inverse MDST 274 | 275 | """ 276 | if transforms is None: 277 | transforms = transforms_default 278 | 279 | kwargs.setdefault('framelength', 2048) 280 | 281 | if not odd: 282 | return stft.ispectrogram( 283 | X, 284 | transform=[ 285 | functools.partial(transforms.imdst, odd=False), 286 | functools.partial(transforms.imdct, odd=False), 287 | ], 288 | halved=False, 289 | **kwargs 290 | ) 291 | else: 292 | return stft.ispectrogram( 293 | X, 294 | transform=transforms.imdst, 295 | halved=False, 296 | **kwargs 297 | ) 298 | 299 | 300 | def cmdct( 301 | x, 302 | odd=True, 303 | transforms=None, 304 | **kwargs 305 | ): 306 | """ Calculate lapped complex MDCT/MCLT of input signal 307 | 308 | Parameters 309 | ---------- 310 | x : array_like 311 | The input signal 312 | odd : boolean, optional 313 | Switch to oddly stacked transform. Defaults to :code:`True`. 314 | transforms : module, optional 315 | Module reference to core transforms. Mostly used to replace 316 | fast with slow core transforms, for testing. Defaults to 317 | :mod:`mdct.fast` 318 | **kwargs, optional 319 | Additional keyword arguments passed to :code:`stft.spectrogram` 320 | 321 | Returns 322 | ------- 323 | out : array_like 324 | The output signal 325 | 326 | See Also 327 | -------- 328 | mdct.fast.transforms.cmdct : complex MDCT 329 | 330 | """ 331 | if transforms is None: 332 | transforms = transforms_default 333 | 334 | return stft.spectrogram( 335 | x, 336 | transform=functools.partial(transforms.cmdct, odd=odd), 337 | halved=False, 338 | **kwargs 339 | ) 340 | 341 | 342 | def icmdct( 343 | X, 344 | odd=True, 345 | transforms=None, 346 | **kwargs 347 | ): 348 | """ Calculate lapped inverse complex MDCT/MCLT of input signal 349 | 350 | Parameters 351 | ---------- 352 | x : array_like 353 | The input signal 354 | odd : boolean, optional 355 | Switch to oddly stacked transform. Defaults to :code:`True`. 356 | transforms : module, optional 357 | Module reference to core transforms. Mostly used to replace 358 | fast with slow core transforms, for testing. Defaults to 359 | :mod:`mdct.fast` 360 | Additional keyword arguments passed to :code:`stft.spectrogram` 361 | 362 | Returns 363 | ------- 364 | out : array_like 365 | The output signal 366 | 367 | See Also 368 | -------- 369 | mdct.fast.transforms.icmdct : inverse complex MDCT 370 | 371 | """ 372 | if transforms is None: 373 | transforms = transforms_default 374 | 375 | return stft.ispectrogram( 376 | X, 377 | transform=functools.partial(transforms.icmdct, odd=odd), 378 | halved=False, 379 | **kwargs 380 | ) 381 | 382 | 383 | mclt = cmdct 384 | imclt = icmdct 385 | -------------------------------------------------------------------------------- /mdct/fast/transforms.py: -------------------------------------------------------------------------------- 1 | """ Module for calculating DCT type 4 using FFT and pre/post-twiddling 2 | 3 | .. warning:: 4 | These core transforms will produce aliasing when used without overlap. 5 | Please use :py:mod:`mdct` unless you know what this means. 6 | 7 | """ 8 | 9 | from __future__ import division 10 | import numpy 11 | import scipy 12 | 13 | __all__ = [ 14 | 'mdct', 'imdct', 15 | 'mdst', 'imdst', 16 | 'cmdct', 'icmdct', 17 | 'mclt', 'imclt', 18 | ] 19 | 20 | 21 | def mdct(x, odd=True): 22 | """ Calculate modified discrete cosine transform of input signal 23 | 24 | Parameters 25 | ---------- 26 | X : array_like 27 | The input signal 28 | odd : boolean, optional 29 | Switch to oddly stacked transform. Defaults to :code:`True`. 30 | 31 | Returns 32 | ------- 33 | out : array_like 34 | The output signal 35 | 36 | """ 37 | return numpy.real(cmdct(x, odd=odd)) * numpy.sqrt(2) 38 | 39 | 40 | def imdct(X, odd=True): 41 | """ Calculate inverse modified discrete cosine transform of input signal 42 | 43 | Parameters 44 | ---------- 45 | X : array_like 46 | The input signal 47 | odd : boolean, optional 48 | Switch to oddly stacked transform. Defaults to :code:`True`. 49 | 50 | Returns 51 | ------- 52 | out : array_like 53 | The output signal 54 | 55 | """ 56 | return icmdct(X, odd=odd) * numpy.sqrt(2) 57 | 58 | 59 | def mdst(x, odd=True): 60 | """ Calculate modified discrete sine transform of input signal 61 | 62 | Parameters 63 | ---------- 64 | X : array_like 65 | The input signal 66 | odd : boolean, optional 67 | Switch to oddly stacked transform. Defaults to :code:`True`. 68 | 69 | Returns 70 | ------- 71 | out : array_like 72 | The output signal 73 | 74 | """ 75 | return -1 * numpy.imag(cmdct(x, odd=odd)) * numpy.sqrt(2) 76 | 77 | 78 | def imdst(X, odd=True): 79 | """ Calculate inverse modified discrete sine transform of input signal 80 | 81 | Parameters 82 | ---------- 83 | X : array_like 84 | The input signal 85 | odd : boolean, optional 86 | Switch to oddly stacked transform. Defaults to :code:`True`. 87 | 88 | Returns 89 | ------- 90 | out : array_like 91 | The output signal 92 | 93 | """ 94 | return -1 * icmdct(X * 1j, odd=odd) * numpy.sqrt(2) 95 | 96 | 97 | def cmdct(x, odd=True): 98 | """ Calculate complex MDCT/MCLT of input signal 99 | 100 | Parameters 101 | ---------- 102 | x : array_like 103 | The input signal 104 | odd : boolean, optional 105 | Switch to oddly stacked transform. Defaults to :code:`True`. 106 | 107 | Returns 108 | ------- 109 | out : array_like 110 | The output signal 111 | 112 | """ 113 | N = len(x) // 2 114 | n0 = (N + 1) / 2 115 | if odd: 116 | outlen = N 117 | pre_twiddle = numpy.exp(-1j * numpy.pi * numpy.arange(N * 2) / (N * 2)) 118 | offset = 0.5 119 | else: 120 | outlen = N + 1 121 | pre_twiddle = 1.0 122 | offset = 0.0 123 | 124 | post_twiddle = numpy.exp( 125 | -1j * numpy.pi * n0 * (numpy.arange(outlen) + offset) / N 126 | ) 127 | 128 | X = scipy.fft.fft(x * pre_twiddle)[:outlen] 129 | 130 | if not odd: 131 | X[0] *= numpy.sqrt(0.5) 132 | X[-1] *= numpy.sqrt(0.5) 133 | 134 | return X * post_twiddle * numpy.sqrt(1 / N) 135 | 136 | 137 | def icmdct(X, odd=True): 138 | """ Calculate inverse complex MDCT/MCLT of input signal 139 | 140 | Parameters 141 | ---------- 142 | X : array_like 143 | The input signal 144 | odd : boolean, optional 145 | Switch to oddly stacked transform. Defaults to :code:`True`. 146 | 147 | Returns 148 | ------- 149 | out : array_like 150 | The output signal 151 | 152 | """ 153 | if not odd and len(X) % 2 == 0: 154 | raise ValueError( 155 | "Even inverse CMDCT requires an odd number " 156 | "of coefficients" 157 | ) 158 | 159 | X = X.copy() 160 | 161 | if odd: 162 | N = len(X) 163 | n0 = (N + 1) / 2 164 | 165 | post_twiddle = numpy.exp( 166 | 1j * numpy.pi * (numpy.arange(N * 2) + n0) / (N * 2) 167 | ) 168 | 169 | Y = numpy.zeros(N * 2, dtype=X.dtype) 170 | Y[:N] = X 171 | Y[N:] = -1 * numpy.conj(X[::-1]) 172 | else: 173 | N = len(X) - 1 174 | n0 = (N + 1) / 2 175 | 176 | post_twiddle = 1.0 177 | 178 | X[0] *= numpy.sqrt(2) 179 | X[-1] *= numpy.sqrt(2) 180 | 181 | Y = numpy.zeros(N * 2, dtype=X.dtype) 182 | Y[:N+1] = X 183 | Y[N+1:] = -1 * numpy.conj(X[-2:0:-1]) 184 | 185 | pre_twiddle = numpy.exp(1j * numpy.pi * n0 * numpy.arange(N * 2) / N) 186 | 187 | y = scipy.fft.ifft(Y * pre_twiddle) 188 | 189 | return numpy.real(y * post_twiddle) * numpy.sqrt(N) 190 | 191 | 192 | mclt = cmdct 193 | imclt = icmdct 194 | -------------------------------------------------------------------------------- /mdct/slow/__init__.py: -------------------------------------------------------------------------------- 1 | """ Module for calculating lapped MDCT using pure Python 2 | 3 | .. warning:: 4 | This module is very slow and only meant for testing. Use :py:mod:`mdct` 5 | instead. 6 | 7 | """ 8 | 9 | import functools 10 | from . import transforms 11 | from .. import fast 12 | 13 | __all__ = [ 14 | 'mdct', 'imdct', 15 | 'mdst', 'imdst', 16 | 'cmdct', 'icmdct', 17 | 'mclt', 'imclt', 18 | ] 19 | 20 | mdct = functools.partial(fast.mdct, transforms=transforms) 21 | imdct = functools.partial(fast.imdct, transforms=transforms) 22 | mdst = functools.partial(fast.mdst, transforms=transforms) 23 | imdst = functools.partial(fast.imdst, transforms=transforms) 24 | cmdct = functools.partial(fast.cmdct, transforms=transforms) 25 | icmdct = functools.partial(fast.icmdct, transforms=transforms) 26 | 27 | mclt = cmdct 28 | imclt = icmdct 29 | -------------------------------------------------------------------------------- /mdct/slow/transforms.py: -------------------------------------------------------------------------------- 1 | """ Module for calculating DCT type 4 using pure Python 2 | 3 | .. warning:: 4 | These core transforms will produce aliasing when used without overlap. 5 | Please use :py:mod:`mdct` unless you know what this means. 6 | 7 | """ 8 | 9 | from __future__ import division 10 | 11 | import numpy 12 | 13 | __all__ = [ 14 | 'mdct', 'imdct', 15 | 'mdst', 'imdst', 16 | 'cmdct', 'icmdct', 17 | 'mclt', 'imclt', 18 | ] 19 | 20 | 21 | def mdct(x, odd=True): 22 | """ Calculate modified discrete cosine transform of input signal in an 23 | inefficient pure-Python method. 24 | 25 | Use only for testing. 26 | 27 | Parameters 28 | ---------- 29 | X : array_like 30 | The input signal 31 | odd : boolean, optional 32 | Switch to oddly stacked transform. Defaults to :code:`True`. 33 | 34 | Returns 35 | ------- 36 | out : array_like 37 | The output signal 38 | 39 | """ 40 | return trans(x, func=numpy.cos, odd=odd) * numpy.sqrt(2) 41 | 42 | 43 | def imdct(X, odd=True): 44 | """ Calculate inverse modified discrete cosine transform of input 45 | signal in an inefficient pure-Python method. 46 | 47 | Use only for testing. 48 | 49 | Parameters 50 | ---------- 51 | X : array_like 52 | The input signal 53 | odd : boolean, optional 54 | Switch to oddly stacked transform. Defaults to :code:`True`. 55 | 56 | Returns 57 | ------- 58 | out : array_like 59 | The output signal 60 | 61 | """ 62 | return itrans(X, func=numpy.cos, odd=odd) * numpy.sqrt(2) 63 | 64 | 65 | def mdst(x, odd=True): 66 | """ Calculate modified discrete sine transform of input signal in an 67 | inefficient pure-Python method. 68 | 69 | Use only for testing. 70 | 71 | Parameters 72 | ---------- 73 | X : array_like 74 | The input signal 75 | odd : boolean, optional 76 | Switch to oddly stacked transform. Defaults to :code:`True`. 77 | 78 | Returns 79 | ------- 80 | out : array_like 81 | The output signal 82 | 83 | """ 84 | return trans(x, func=numpy.sin, odd=odd) * numpy.sqrt(2) 85 | 86 | 87 | def imdst(X, odd=True): 88 | """ Calculate inverse modified discrete sine transform of input 89 | signal in an inefficient pure-Python method. 90 | 91 | Use only for testing. 92 | 93 | Parameters 94 | ---------- 95 | X : array_like 96 | The input signal 97 | odd : boolean, optional 98 | Switch to oddly stacked transform. Defaults to :code:`True`. 99 | 100 | Returns 101 | ------- 102 | out : array_like 103 | The output signal 104 | 105 | """ 106 | return itrans(X, func=numpy.sin, odd=odd) * numpy.sqrt(2) 107 | 108 | 109 | def cmdct(x, odd=True): 110 | """ Calculate complex modified discrete cosine transform of input 111 | inefficient pure-Python method. 112 | 113 | Use only for testing. 114 | 115 | Parameters 116 | ---------- 117 | X : array_like 118 | The input signal 119 | odd : boolean, optional 120 | Switch to oddly stacked transform. Defaults to :code:`True`. 121 | 122 | Returns 123 | ------- 124 | out : array_like 125 | The output signal 126 | 127 | """ 128 | return trans(x, func=lambda x: numpy.cos(x) - 1j * numpy.sin(x), odd=odd) 129 | 130 | 131 | def icmdct(X, odd=True): 132 | """ Calculate inverse complex modified discrete cosine transform of input 133 | signal in an inefficient pure-Python method. 134 | 135 | Use only for testing. 136 | 137 | Parameters 138 | ---------- 139 | X : array_like 140 | The input signal 141 | odd : boolean, optional 142 | Switch to oddly stacked transform. Defaults to :code:`True`. 143 | 144 | Returns 145 | ------- 146 | out : array_like 147 | The output signal 148 | 149 | """ 150 | return itrans(X, func=lambda x: numpy.cos(x) + 1j * numpy.sin(x), odd=odd) 151 | 152 | 153 | mclt = cmdct 154 | imclt = icmdct 155 | 156 | 157 | def trans(x, func, odd=True): 158 | """ Calculate modified discrete sine/cosine transform of input signal in an 159 | inefficient pure-Python method. 160 | 161 | Use only for testing. 162 | 163 | Parameters 164 | ---------- 165 | X : array_like 166 | The input signal 167 | func : callable 168 | The transform kernel function 169 | odd : boolean, optional 170 | Switch to oddly stacked transform. Defaults to :code:`True`. 171 | 172 | Returns 173 | ------- 174 | out : array_like 175 | The output signal 176 | 177 | """ 178 | N = len(x) // 2 179 | if odd: 180 | outlen = N 181 | offset = 0.5 182 | else: 183 | outlen = N + 1 184 | offset = 0.0 185 | 186 | X = numpy.zeros(outlen, dtype=numpy.complex) 187 | n = numpy.arange(len(x)) 188 | 189 | for k in range(len(X)): 190 | X[k] = numpy.sum( 191 | x * func( 192 | (numpy.pi / N) * ( 193 | n + 0.5 + N / 2 194 | ) * ( 195 | k + offset 196 | ) 197 | ) 198 | ) 199 | 200 | if not odd: 201 | X[0] *= numpy.sqrt(0.5) 202 | X[-1] *= numpy.sqrt(0.5) 203 | 204 | return X * numpy.sqrt(1 / N) 205 | 206 | 207 | def itrans(X, func, odd=True): 208 | """ Calculate inverse modified discrete sine/cosine transform of input 209 | signal in an inefficient pure-Python method. 210 | 211 | Use only for testing. 212 | 213 | Parameters 214 | ---------- 215 | X : array_like 216 | The input signal 217 | func : callable 218 | The transform kernel function 219 | odd : boolean, optional 220 | Switch to oddly stacked transform. Defaults to :code:`True`. 221 | 222 | Returns 223 | ------- 224 | out : array_like 225 | The output signal 226 | 227 | """ 228 | if not odd and len(X) % 2 == 0: 229 | raise ValueError( 230 | "Even inverse CMDCT requires an odd number " 231 | "of coefficients" 232 | ) 233 | 234 | X = X.copy() 235 | 236 | if odd: 237 | N = len(X) 238 | offset = 0.5 239 | else: 240 | N = len(X) - 1 241 | offset = 0.0 242 | 243 | X[0] *= numpy.sqrt(0.5) 244 | X[-1] *= numpy.sqrt(0.5) 245 | 246 | x = numpy.zeros(N * 2, dtype=numpy.complex) 247 | k = numpy.arange(len(X)) 248 | 249 | for n in range(len(x)): 250 | x[n] = numpy.sum( 251 | X * func( 252 | (numpy.pi / N) * ( 253 | n + 0.5 + N / 2 254 | ) * ( 255 | k + offset 256 | ) 257 | ) 258 | ) 259 | 260 | return numpy.real(x) * numpy.sqrt(1 / N) 261 | -------------------------------------------------------------------------------- /mdct/windows.py: -------------------------------------------------------------------------------- 1 | """ Module for windowing functions not found in SciPy 2 | 3 | """ 4 | 5 | from __future__ import division 6 | import numpy as np 7 | from scipy.signal import kaiser 8 | 9 | __all__ = [ 10 | 'kaiser_derived', 11 | ] 12 | 13 | 14 | def kaiser_derived(M, beta): 15 | """ Return a Kaiser-Bessel derived window. 16 | 17 | Parameters 18 | ---------- 19 | M : int 20 | Number of points in the output window. If zero or less, an empty 21 | array is returned. 22 | beta : float 23 | Kaiser-Bessel window shape parameter. 24 | 25 | Returns 26 | ------- 27 | w : ndarray 28 | The window, normalized to fulfil the Princen-Bradley condition. 29 | 30 | Notes 31 | ----- 32 | This window is only defined for an even number of taps. 33 | 34 | References 35 | ---------- 36 | .. [1] Wikipedia, "Kaiser window", 37 | https://en.wikipedia.org/wiki/Kaiser_window 38 | 39 | """ 40 | M = int(M) 41 | try: 42 | from scipy.signal import kaiser_derived as scipy_kd 43 | return scipy_kd(M, beta) 44 | except ImportError: 45 | pass 46 | 47 | if M < 1: 48 | return np.array([]) 49 | 50 | if M % 2: 51 | raise ValueError( 52 | "Kaiser Bessel Derived windows are only defined for even number " 53 | "of taps" 54 | ) 55 | 56 | w = np.zeros(M) 57 | kaiserw = kaiser(M // 2 + 1, beta) 58 | csum = np.cumsum(kaiserw) 59 | halfw = np.sqrt(csum[:-1] / csum[-1]) 60 | w[:M//2] = halfw 61 | w[-M//2:] = halfw[::-1] 62 | 63 | return w 64 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This is only needed for Read The Docs 2 | pip >= 8.0.0 3 | wheel 4 | numpy>=1.6 5 | scipy>=0.13.0 6 | stft>=0.5.2 7 | sphinx 8 | sphinx_rtd_theme 9 | numpydoc 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [tool:pytest] 2 | addopts = --ignore scribble.py --doctest-modules --cov-report term-missing --cov mdct --pycodestyle 3 | 4 | [bdist_wheel] 5 | universal = 1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import setuptools 4 | 5 | if __name__ == "__main__": 6 | setuptools.setup( 7 | name='mdct', 8 | version='0.4', 9 | 10 | description='A fast MDCT implementation using SciPy and FFTs', 11 | author='Nils Werner', 12 | author_email='nils.werner@gmail.com', 13 | url='http://mdct.readthedocs.io/', 14 | 15 | license='MIT', 16 | packages=setuptools.find_packages(), 17 | 18 | install_requires=[ 19 | 'numpy>=1.6', 20 | 'scipy>=0.13.0', 21 | 'stft>=0.5.2', 22 | ], 23 | extras_require={ 24 | 'tests': [ 25 | 'pytest', 26 | 'pytest-cov', 27 | 'pytest-pycodestyle', 28 | 'tox', 29 | ], 30 | 'docs': [ 31 | 'sphinx<=1.3', 32 | 'sphinx_rtd_theme', 33 | 'numpydoc', 34 | ], 35 | }, 36 | tests_require=[ 37 | 'pytest', 38 | 'pytest-cov', 39 | 'pytest-pycodestyle', 40 | ], 41 | 42 | classifiers=[ 43 | 'Development Status :: 4 - Beta', 44 | 'Intended Audience :: Telecommunications Industry', 45 | 'Intended Audience :: Science/Research', 46 | 'License :: OSI Approved :: MIT License', 47 | 'Programming Language :: Python :: 2.7', 48 | 'Programming Language :: Python :: 3', 49 | 'Topic :: Multimedia :: Sound/Audio :: Analysis', 50 | 'Topic :: Multimedia :: Sound/Audio :: Sound Synthesis' 51 | ], 52 | 53 | zip_safe=True, 54 | include_package_data=True, 55 | ) 56 | -------------------------------------------------------------------------------- /tests/test_all.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import itertools 3 | import numpy 4 | import scipy.signal 5 | import mdct 6 | import mdct.slow 7 | import stft 8 | 9 | 10 | fast_functions = [ 11 | (mdct.fast.mdct, mdct.fast.imdct), 12 | (mdct.fast.mdst, mdct.fast.imdst), 13 | (mdct.fast.cmdct, mdct.fast.icmdct) 14 | ] 15 | 16 | slow_functions = [ 17 | (mdct.slow.mdct, mdct.slow.imdct), 18 | (mdct.slow.mdst, mdct.slow.imdst), 19 | (mdct.slow.cmdct, mdct.slow.icmdct) 20 | ] 21 | 22 | fast_unlapped_functions = [ 23 | (mdct.fast.transforms.cmdct, mdct.fast.transforms.icmdct), 24 | ] 25 | 26 | slow_unlapped_functions = [ 27 | (mdct.slow.transforms.cmdct, mdct.slow.transforms.icmdct), 28 | ] 29 | 30 | 31 | def correspondences(a, b): 32 | """ Returns 33 | 34 | - fast.forwards->slow.forwards 35 | - fast.backwards->slow.backwards 36 | 37 | """ 38 | return list(zip(a, b)) 39 | 40 | 41 | def merge(a, b): 42 | """ Returns 43 | 44 | - slow.forwards->slow.backwards 45 | - fast.forwards->fast.backwards 46 | 47 | """ 48 | return a + b 49 | 50 | 51 | def combinations(a, b): 52 | """ Returns all combinations of 53 | 54 | - fast.forwards->fast.backwards 55 | - fast.forwards->slow.backwards 56 | - slow.forwards->fast.backwards 57 | - slow.forwards->slow.backwards 58 | 59 | """ 60 | return list(itertools.chain.from_iterable( 61 | [ 62 | list(itertools.product(*zip(*item))) 63 | for item in correspondences(a, b) 64 | ] 65 | )) 66 | 67 | 68 | def crossings(a, b): 69 | """ Returns 70 | 71 | - fast.forwards->slow.backwards 72 | - slow.forwards->fast.backwards 73 | 74 | but never 75 | 76 | - slow.forwards->slow.backwards 77 | - fast.forwards->fast.backwards 78 | 79 | """ 80 | return [ 81 | item for item in combinations(a, b) 82 | if item not in itertools.chain.from_iterable(correspondences(a, b)) 83 | ] 84 | 85 | 86 | def forward(a, b): 87 | """ Returns 88 | 89 | - fast.forwards->slow.forwards 90 | 91 | """ 92 | return [(x, y) for (x, _), (y, _) in correspondences(a, b)] 93 | 94 | 95 | def backward(a, b): 96 | """ Returns 97 | 98 | - fast.backwards->slow.backwards 99 | 100 | """ 101 | return [(x, y) for (_, x), (_, y) in correspondences(a, b)] 102 | 103 | 104 | corresponding_functions = correspondences(fast_functions, slow_functions) 105 | any_functions = merge(fast_functions, slow_functions) 106 | all_functions = combinations(fast_functions, slow_functions) 107 | cross_functions = crossings(fast_functions, slow_functions) 108 | forward_functions = forward(fast_functions, slow_functions) 109 | backward_functions = backward(fast_functions, slow_functions) 110 | 111 | unlapped_functions = combinations( 112 | fast_unlapped_functions, slow_unlapped_functions 113 | ) 114 | 115 | forward_unlapped_functions = \ 116 | forward(fast_unlapped_functions, slow_unlapped_functions) 117 | 118 | backward_unlapped_functions = \ 119 | backward(fast_unlapped_functions, slow_unlapped_functions) 120 | 121 | 122 | def test_halving(sig, module, odd): 123 | # 124 | # Test if the output is half the size of the input 125 | # 126 | if odd: 127 | offset = 0 128 | else: 129 | offset = 1 130 | 131 | assert len(module.transforms.mdct(sig, odd=odd)) == len(sig) // 2 + offset 132 | assert len(module.transforms.mdst(sig, odd=odd)) == len(sig) // 2 + offset 133 | assert len(module.transforms.cmdct(sig, odd=odd)) == len(sig) // 2 + offset 134 | 135 | 136 | def test_outtypes(sig, backsig, module, odd): 137 | assert numpy.all(numpy.isreal(module.transforms.mdct(sig, odd=odd))) 138 | assert numpy.all(numpy.isreal(module.transforms.mdst(sig, odd=odd))) 139 | assert numpy.any(numpy.iscomplex(module.transforms.cmdct(sig, odd=odd))) 140 | assert numpy.all(numpy.isreal(module.transforms.icmdct(backsig, odd=odd))) 141 | 142 | 143 | @pytest.mark.parametrize("function", forward_functions) 144 | def test_forward_equality(sig, function, odd, window, framelength): 145 | # 146 | # Test if slow and fast transforms are equal. Tests all with lapping. 147 | # 148 | spec = function[0](sig, odd=odd, window=window, framelength=framelength) 149 | spec2 = function[1](sig, odd=odd, window=window, framelength=framelength) 150 | 151 | assert spec.shape == spec2.shape 152 | assert numpy.allclose(spec, spec2) 153 | 154 | 155 | @pytest.mark.parametrize("function", forward_functions) 156 | def test_energy_convervation(sig, function, odd, window, framelength): 157 | # 158 | # Test if slow and fast transforms are equal. Tests all with lapping. 159 | # 160 | spec = function[0](sig, odd=odd, window=window, framelength=framelength) 161 | spec2 = function[1](sig, odd=odd, window=window, framelength=framelength) 162 | 163 | assert numpy.allclose( 164 | numpy.sum(sig ** 2), numpy.sum(spec ** 2), atol=100, rtol=100 165 | ) 166 | assert numpy.allclose( 167 | numpy.sum(sig ** 2), numpy.sum(spec2 ** 2), atol=100, rtol=100 168 | ) 169 | 170 | 171 | @pytest.mark.parametrize("function", backward_functions) 172 | def test_backward_equality(spectrum, function, odd, window, framelength): 173 | # 174 | # Test if slow and fast inverse transforms are equal. 175 | # Tests all with lapping. 176 | # 177 | sig = function[0]( 178 | spectrum, odd=odd, window=window, framelength=framelength 179 | ) 180 | sig2 = function[1]( 181 | spectrum, odd=odd, window=window, framelength=framelength 182 | ) 183 | 184 | assert sig.shape == sig2.shape 185 | assert numpy.allclose(sig, sig2) 186 | 187 | 188 | @pytest.mark.parametrize("function", all_functions) 189 | def test_inverse(sig, function, odd, window, framelength): 190 | # 191 | # Test if combinations slow-slow, slow-fast, fast-fast, fast-slow are all 192 | # perfect reconstructing. Tests all with lapping. 193 | # 194 | spec = function[0](sig, odd=odd, window=window, framelength=framelength) 195 | outsig = function[1](spec, odd=odd, window=window, framelength=framelength) 196 | 197 | assert numpy.all(numpy.isreal(outsig)) 198 | assert len(outsig) == len(sig) 199 | assert numpy.allclose(outsig, sig) 200 | 201 | 202 | @pytest.mark.parametrize("function", unlapped_functions) 203 | def test_unlapped_inverse(sig, function, odd): 204 | # 205 | # Test if combinations slow-slow, slow-fast, fast-fast, fast-slow are all 206 | # perfect reconstructing. Tests only CMDCT without lapping. 207 | # 208 | spec = function[0](sig, odd=odd) 209 | outsig = function[1](spec, odd=odd) 210 | 211 | assert len(outsig) == len(sig) 212 | assert numpy.allclose(outsig, sig) 213 | 214 | 215 | @pytest.mark.parametrize("function", forward_unlapped_functions) 216 | def test_unlapped_equality(sig, function, odd): 217 | # 218 | # Test if slow and fast unlapped transforms are equal. Tests only 219 | # CDMCT without lapping. 220 | # 221 | outsig = function[0](sig, odd=odd) 222 | outsig2 = function[1](sig, odd=odd) 223 | 224 | assert outsig.shape == outsig2.shape 225 | assert numpy.allclose(outsig, outsig2) 226 | 227 | 228 | @pytest.mark.parametrize("function", backward_unlapped_functions) 229 | def test_unlapped_backwards_equality(backsig, function, odd): 230 | # 231 | # Test if slow and fast unlapped inverse transforms are equal. Tests only 232 | # CDMCT without lapping. 233 | # 234 | outsig = function[0](backsig, odd=odd) 235 | outsig2 = function[1](backsig, odd=odd) 236 | 237 | assert outsig.shape == outsig2.shape 238 | assert numpy.allclose(outsig, outsig2) 239 | -------------------------------------------------------------------------------- /tests/test_issues.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import pytest 3 | import mdct 4 | 5 | 6 | def test_weird_backtransform(sig, framelength): 7 | spec = numpy.array(mdct.mdct( 8 | sig, 9 | framelength=framelength, 10 | )) 11 | 12 | outsig = mdct.imdct( 13 | spec, 14 | framelength=framelength, 15 | outlength=len(sig) 16 | ) 17 | 18 | assert sig.shape == outsig.shape 19 | assert numpy.allclose(sig, outsig) 20 | -------------------------------------------------------------------------------- /tests/test_windows.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy 3 | import mdct.windows 4 | 5 | 6 | def test_kbd(): 7 | M = 100 8 | w = mdct.windows.kaiser_derived(M, beta=4.) 9 | 10 | assert numpy.allclose(w[:M//2] ** 2 + w[-M//2:] ** 2, 1.) 11 | 12 | with pytest.raises(ValueError): 13 | mdct.windows.kaiser_derived(M + 1, beta=4.) 14 | 15 | assert numpy.allclose( 16 | mdct.windows.kaiser_derived(2, beta=numpy.pi/2)[:1], 17 | [numpy.sqrt(2)/2]) 18 | 19 | assert numpy.allclose( 20 | mdct.windows.kaiser_derived(4, beta=numpy.pi/2)[:2], 21 | [0.518562710536, 0.855039598640]) 22 | 23 | assert numpy.allclose( 24 | mdct.windows.kaiser_derived(6, beta=numpy.pi/2)[:3], 25 | [0.436168993154, 0.707106781187, 0.899864772847]) 26 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py35 3 | [testenv] 4 | deps=pytest 5 | commands= 6 | pip install -e .[tests,docs] 7 | py.test {posargs} 8 | --------------------------------------------------------------------------------