Hello community, here is the log from the commit of package python-sounddevice for openSUSE:Factory checked in at 2020-07-30 10:02:01 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-sounddevice (Old) and /work/SRC/openSUSE:Factory/.python-sounddevice.new.3592 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-sounddevice" Thu Jul 30 10:02:01 2020 rev:9 rq:823365 version:0.4.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-sounddevice/python-sounddevice.changes 2020-03-26 23:35:22.094820409 +0100 +++ /work/SRC/openSUSE:Factory/.python-sounddevice.new.3592/python-sounddevice.changes 2020-07-30 10:03:40.871364510 +0200 @@ -1,0 +2,9 @@ +Wed Jul 29 07:27:48 UTC 2020 - Marketa Calabkova <[email protected]> + +- Update to 0.4.0 + * Drop support for Python 2.x + * Fix memory issues in `play()`, `rec()` and `playrec()` + * Example application ``play_stream.py`` + * Remove ``print_devices()``, `query_devices()` can be used instead + +------------------------------------------------------------------- Old: ---- sounddevice-0.3.15.tar.gz New: ---- sounddevice-0.4.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-sounddevice.spec ++++++ --- /var/tmp/diff_new_pack.s0AstM/_old 2020-07-30 10:03:42.227365646 +0200 +++ /var/tmp/diff_new_pack.s0AstM/_new 2020-07-30 10:03:42.227365646 +0200 @@ -17,8 +17,9 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} +%define skip_python2 1 Name: python-sounddevice -Version: 0.3.15 +Version: 0.4.0 Release: 0 Summary: Module to play and record sound with Python License: MIT ++++++ sounddevice-0.3.15.tar.gz -> sounddevice-0.4.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/CONTRIBUTING.rst new/sounddevice-0.4.0/CONTRIBUTING.rst --- old/sounddevice-0.3.15/CONTRIBUTING.rst 2019-09-20 09:17:44.000000000 +0200 +++ new/sounddevice-0.4.0/CONTRIBUTING.rst 2020-07-15 17:23:35.000000000 +0200 @@ -1,17 +1,98 @@ Contributing ------------- +============ If you find bugs, errors, omissions or other things that need improvement, please create an issue or a pull request at https://github.com/spatialaudio/python-sounddevice/. Contributions are always welcome! + +Reporting Problems +------------------ + +When creating an issue at +https://github.com/spatialaudio/python-sounddevice/issues, +please make sure to provide as much useful information as possible. + +You can use Markdown formatting to show Python code, e.g. :: + + I have created a script named `my_script.py`: + + ```python + import sounddevice as sd + + fs = 48000 + duration = 1.5 + + data = sd.rec(int(duration * fs), channels=99) + sd.wait() + print(data.shape) + ``` + +Please provide minimal code +(remove everything that's not necessary to show the problem), +but make sure that the code example still has everything that's needed to run it, +including all ``import`` statements. + +You should of course also show what happens when you run your code, e.g. :: + + Running my script, I got this error: + + ``` + $ python3 my_script.py + Expression 'parameters->channelCount <= maxChans' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1514 + Expression 'ValidateParameters( inputParameters, hostApi, StreamDirection_In )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2818 + Traceback (most recent call last): + File "my_script.py", line 6, in <module> + data = sd.rec(int(duration * fs), channels=99) + ... + sounddevice.PortAudioError: Error opening InputStream: Invalid number of channels [PaErrorCode -9998] + ``` + +Please remember to provide the full command invocation and the full output. +You should only remove lines of output when you know they are irrelevant. + +You should also mention the operating system and host API you are using +(e.g. "Linux/ALSA" or "macOS/Core Audio" or "Windows/WASAPI"). + +If your problem is related to a certain hardware device, +you should provide the list of devices as reported by :: + + python3 -m sounddevice + +If your problem has to do with the version of the PortAudio library you are using, +you should provide the output of this script:: + + import sounddevice as sd + print(sd._libname) + print(sd.get_portaudio_version()) + +If you don't want to clutter the issue description with a huge load of gibberish, +you can use the ``<details>`` HTML tag to show some content only on demand:: + + <details> + + ``` + $ python3 -m sounddevice + 0 Built-in Line Input, Core Audio (2 in, 0 out) + > 1 Built-in Digital Input, Core Audio (2 in, 0 out) + < 2 Built-in Output, Core Audio (0 in, 2 out) + 3 Built-in Line Output, Core Audio (0 in, 2 out) + 4 Built-in Digital Output, Core Audio (0 in, 2 out) + ``` + + </details> + + +Development Installation +------------------------ + Instead of pip-installing the latest release from PyPI_, you should get the -newest development version from Github_:: +newest development version (a.k.a. "master") from Github_:: git clone --recursive https://github.com/spatialaudio/python-sounddevice.git cd python-sounddevice - python3 setup.py develop --user + python3 -m pip install -e . --user .. _PyPI: https://pypi.org/project/sounddevice/ .. _Github: https://github.com/spatialaudio/python-sounddevice/ @@ -19,12 +100,6 @@ This way, your installation always stays up-to-date, even if you pull new changes from the Github repository. -If you prefer, you can also replace the last command with:: - - python3 -m pip install --user -e . - -... where ``-e`` stands for ``--editable``. - Whenever the file ``sounddevice_build.py`` changes (either because you edited it or it was updated by pulling from Github or switching branches), you have to run the last command again. @@ -35,13 +110,17 @@ git submodule update --init + +Building the Documentation +-------------------------- + If you make changes to the documentation, you can locally re-create the HTML pages using Sphinx_. You can install it and a few other necessary packages with:: python3 -m pip install -r doc/requirements.txt --user -To create the HTML pages, use:: +To (re-)build the HTML files, use:: python3 setup.py build_sphinx diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/NEWS.rst new/sounddevice-0.4.0/NEWS.rst --- old/sounddevice-0.3.15/NEWS.rst 2020-03-18 10:11:15.000000000 +0100 +++ new/sounddevice-0.4.0/NEWS.rst 2020-07-18 15:53:31.000000000 +0200 @@ -1,3 +1,8 @@ +0.4.0 (2020-07-18): + * Drop support for Python 2.x + * Fix memory issues in `play()`, `rec()` and `playrec()` + * Example application ``play_stream.py`` + 0.3.15 (2020-03-18): * This will be the last release supporting Python 2.x! @@ -53,7 +58,7 @@ * Send PortAudio output to ``/dev/null`` (on Linux and OSX) 0.3.0 (2015-10-28): - * Remove `print_devices()`, `query_devices()` can be used instead, + * Remove ``print_devices()``, `query_devices()` can be used instead, since it now returns a `DeviceList` object. 0.2.2 (2015-10-21): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/PKG-INFO new/sounddevice-0.4.0/PKG-INFO --- old/sounddevice-0.3.15/PKG-INFO 2020-03-18 10:14:15.994104400 +0100 +++ new/sounddevice-0.4.0/PKG-INFO 2020-07-18 15:54:17.558579400 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: sounddevice -Version: 0.3.15 +Version: 0.4.0 Summary: Play and Record Sound with Python Home-page: http://python-sounddevice.readthedocs.io/ Author: Matthias Geier @@ -32,8 +32,7 @@ Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Multimedia :: Sound/Audio -Requires-Python: >=2.6 +Requires-Python: >=3 Provides-Extra: NumPy diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/doc/CONTRIBUTING.rst new/sounddevice-0.4.0/doc/CONTRIBUTING.rst --- old/sounddevice-0.3.15/doc/CONTRIBUTING.rst 2019-09-20 09:17:44.000000000 +0200 +++ new/sounddevice-0.4.0/doc/CONTRIBUTING.rst 2020-07-15 17:23:35.000000000 +0200 @@ -1,17 +1,98 @@ Contributing ------------- +============ If you find bugs, errors, omissions or other things that need improvement, please create an issue or a pull request at https://github.com/spatialaudio/python-sounddevice/. Contributions are always welcome! + +Reporting Problems +------------------ + +When creating an issue at +https://github.com/spatialaudio/python-sounddevice/issues, +please make sure to provide as much useful information as possible. + +You can use Markdown formatting to show Python code, e.g. :: + + I have created a script named `my_script.py`: + + ```python + import sounddevice as sd + + fs = 48000 + duration = 1.5 + + data = sd.rec(int(duration * fs), channels=99) + sd.wait() + print(data.shape) + ``` + +Please provide minimal code +(remove everything that's not necessary to show the problem), +but make sure that the code example still has everything that's needed to run it, +including all ``import`` statements. + +You should of course also show what happens when you run your code, e.g. :: + + Running my script, I got this error: + + ``` + $ python3 my_script.py + Expression 'parameters->channelCount <= maxChans' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1514 + Expression 'ValidateParameters( inputParameters, hostApi, StreamDirection_In )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2818 + Traceback (most recent call last): + File "my_script.py", line 6, in <module> + data = sd.rec(int(duration * fs), channels=99) + ... + sounddevice.PortAudioError: Error opening InputStream: Invalid number of channels [PaErrorCode -9998] + ``` + +Please remember to provide the full command invocation and the full output. +You should only remove lines of output when you know they are irrelevant. + +You should also mention the operating system and host API you are using +(e.g. "Linux/ALSA" or "macOS/Core Audio" or "Windows/WASAPI"). + +If your problem is related to a certain hardware device, +you should provide the list of devices as reported by :: + + python3 -m sounddevice + +If your problem has to do with the version of the PortAudio library you are using, +you should provide the output of this script:: + + import sounddevice as sd + print(sd._libname) + print(sd.get_portaudio_version()) + +If you don't want to clutter the issue description with a huge load of gibberish, +you can use the ``<details>`` HTML tag to show some content only on demand:: + + <details> + + ``` + $ python3 -m sounddevice + 0 Built-in Line Input, Core Audio (2 in, 0 out) + > 1 Built-in Digital Input, Core Audio (2 in, 0 out) + < 2 Built-in Output, Core Audio (0 in, 2 out) + 3 Built-in Line Output, Core Audio (0 in, 2 out) + 4 Built-in Digital Output, Core Audio (0 in, 2 out) + ``` + + </details> + + +Development Installation +------------------------ + Instead of pip-installing the latest release from PyPI_, you should get the -newest development version from Github_:: +newest development version (a.k.a. "master") from Github_:: git clone --recursive https://github.com/spatialaudio/python-sounddevice.git cd python-sounddevice - python3 setup.py develop --user + python3 -m pip install -e . --user .. _PyPI: https://pypi.org/project/sounddevice/ .. _Github: https://github.com/spatialaudio/python-sounddevice/ @@ -19,12 +100,6 @@ This way, your installation always stays up-to-date, even if you pull new changes from the Github repository. -If you prefer, you can also replace the last command with:: - - python3 -m pip install --user -e . - -... where ``-e`` stands for ``--editable``. - Whenever the file ``sounddevice_build.py`` changes (either because you edited it or it was updated by pulling from Github or switching branches), you have to run the last command again. @@ -35,13 +110,17 @@ git submodule update --init + +Building the Documentation +-------------------------- + If you make changes to the documentation, you can locally re-create the HTML pages using Sphinx_. You can install it and a few other necessary packages with:: python3 -m pip install -r doc/requirements.txt --user -To create the HTML pages, use:: +To (re-)build the HTML files, use:: python3 setup.py build_sphinx diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/doc/api/expert-mode.rst new/sounddevice-0.4.0/doc/api/expert-mode.rst --- old/sounddevice-0.3.15/doc/api/expert-mode.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/sounddevice-0.4.0/doc/api/expert-mode.rst 2020-07-15 17:23:35.000000000 +0200 @@ -0,0 +1,20 @@ +Expert Mode +=========== + +.. currentmodule:: sounddevice + +.. autosummary:: + :nosignatures: + + _initialize + _terminate + _split + _StreamBase + +.. autofunction:: _initialize + +.. autofunction:: _terminate + +.. autofunction:: _split + +.. autoclass:: _StreamBase diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/doc/api/index.rst new/sounddevice-0.4.0/doc/api/index.rst --- old/sounddevice-0.3.15/doc/api/index.rst 2019-10-25 11:23:27.000000000 +0200 +++ new/sounddevice-0.4.0/doc/api/index.rst 2020-07-15 17:23:35.000000000 +0200 @@ -14,3 +14,4 @@ streams raw-streams misc + expert-mode diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/doc/conf.py new/sounddevice-0.4.0/doc/conf.py --- old/sounddevice-0.3.15/doc/conf.py 2020-03-18 09:54:19.000000000 +0100 +++ new/sounddevice-0.4.0/doc/conf.py 2020-07-15 17:23:35.000000000 +0200 @@ -1,24 +1,8 @@ -# -*- coding: utf-8 -*- -# -# python-sounddevice documentation build configuration file, created by -# sphinx-quickstart on Fri Jun 5 15:56:15 2015. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - +"""Configuration file for Sphinx.""" import sys import os from subprocess import check_output -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('.')) @@ -26,14 +10,6 @@ import fake__sounddevice sys.modules['_sounddevice'] = sys.modules['fake__sounddevice'] -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.3' # for sphinx.ext.napoleon - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', @@ -61,237 +37,59 @@ 'numpy': ('https://docs.scipy.org/doc/numpy/', None), } -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. master_doc = 'index' -# General information about the project. authors = 'Matthias Geier' project = 'python-sounddevice' copyright = '2020, ' + authors -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -#version = '0.0.0' -# The full version, including alpha/beta/rc tags. try: release = check_output(['git', 'describe', '--tags', '--always']) release = release.decode().strip() except Exception: release = '<unknown>' -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' try: today = check_output(['git', 'show', '-s', '--format=%ad', '--date=short']) today = today.decode().strip() except Exception: today = '<unknown date>' -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. default_role = 'any' -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +nitpicky = True - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. html_theme_options = { 'collapse_navigation': False, } - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# "<project> v<release> documentation". html_title = project + ', version ' + release - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -#html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. html_domain_indices = False - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a <link> tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. +html_show_copyright = False htmlhelp_basename = 'python-sounddevice' - -# -- Options for LaTeX output --------------------------------------------- - latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). 'papersize': 'a4paper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. #'preamble': '', - 'printindex': '', } - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). latex_documents = [('index', 'python-sounddevice.tex', project, authors, 'howto')] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. latex_show_urls = 'footnote' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. latex_domain_indices = False -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -#man_pages = [('index', project, project, [authors], 1)] - -# If true, show URL addresses after external links. -#man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -#texinfo_documents = [ -# ('index', project, project, authors, project, -# 'Play and Record Sound with Python.', -# 'Miscellaneous'), -#] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True +def gh_example_role(rolename, rawtext, text, lineno, inliner, + options={}, content=()): + from docutils import nodes, utils + github_url = 'https://github.com/spatialaudio/python-sounddevice' + base_url = github_url + '/blob/' + release + '/examples/%s' + text = utils.unescape(text) + full_url = base_url % text + pnode = nodes.reference(internal=False, refuri=full_url) + pnode += nodes.literal(text, text, classes=['file']) + return [pnode], [] -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' -# If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +def setup(app): + app.add_role('gh-example', gh_example_role) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/doc/examples.rst new/sounddevice-0.4.0/doc/examples.rst --- old/sounddevice-0.3.15/doc/examples.rst 2019-09-20 09:17:44.000000000 +0200 +++ new/sounddevice-0.4.0/doc/examples.rst 2020-07-15 17:23:35.000000000 +0200 @@ -3,62 +3,69 @@ Play a Sound File ----------------- -:download:`play_file.py <../examples/play_file.py>` +:gh-example:`play_file.py` .. literalinclude:: ../examples/play_file.py Play a Very Long Sound File --------------------------- -:download:`play_long_file.py <../examples/play_long_file.py>` +:gh-example:`play_long_file.py` .. literalinclude:: ../examples/play_long_file.py +Play a Web Stream +----------------- + +:gh-example:`play_stream.py` + +.. literalinclude:: ../examples/play_stream.py + Play a Sine Signal ------------------ -:download:`play_sine.py <../examples/play_sine.py>` +:gh-example:`play_sine.py` .. literalinclude:: ../examples/play_sine.py Input to Output Pass-Through ---------------------------- -:download:`wire.py <../examples/wire.py>` +:gh-example:`wire.py` .. literalinclude:: ../examples/wire.py Plot Microphone Signal(s) in Real-Time -------------------------------------- -:download:`plot_input.py <../examples/plot_input.py>` +:gh-example:`plot_input.py` .. literalinclude:: ../examples/plot_input.py Real-Time Text-Mode Spectrogram ------------------------------- -:download:`spectrogram.py <../examples/spectrogram.py>` +:gh-example:`spectrogram.py` .. literalinclude:: ../examples/spectrogram.py Recording with Arbitrary Duration --------------------------------- -:download:`rec_unlimited.py <../examples/rec_unlimited.py>` +:gh-example:`rec_unlimited.py` .. literalinclude:: ../examples/rec_unlimited.py Using a stream in an `asyncio` coroutine ---------------------------------------- -:download:`asyncio_coroutines.py <../examples/asyncio_coroutines.py>` +:gh-example:`asyncio_coroutines.py` .. literalinclude:: ../examples/asyncio_coroutines.py Creating an `asyncio` generator for audio blocks ------------------------------------------------ -:download:`asyncio_generators.py <../examples/asyncio_generators.py>` +:gh-example:`asyncio_generators.py` .. literalinclude:: ../examples/asyncio_generators.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/doc/installation.rst new/sounddevice-0.4.0/doc/installation.rst --- old/sounddevice-0.3.15/doc/installation.rst 2019-09-20 09:17:44.000000000 +0200 +++ new/sounddevice-0.4.0/doc/installation.rst 2020-07-15 17:23:35.000000000 +0200 @@ -85,7 +85,7 @@ .. _PortAudio: http://www.portaudio.com/ .. _NumPy: https://numpy.org/ .. _Python: https://www.python.org/ -.. _Anaconda: https://www.anaconda.com/distribution/#download-section +.. _Anaconda: https://www.anaconda.com/products/individual#Downloads .. _WinPython: https://winpython.github.io/ .. _CFFI: https://cffi.readthedocs.io/ .. _PyPI: https://pypi.org/project/sounddevice/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/doc/version-history.rst new/sounddevice-0.4.0/doc/version-history.rst --- old/sounddevice-0.3.15/doc/version-history.rst 2019-09-20 09:17:44.000000000 +0200 +++ new/sounddevice-0.4.0/doc/version-history.rst 2020-07-15 17:23:35.000000000 +0200 @@ -3,8 +3,4 @@ .. currentmodule:: sounddevice -.. default-role:: py:obj - .. include:: ../NEWS.rst - -.. default-role:: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/examples/play_stream.py new/sounddevice-0.4.0/examples/play_stream.py --- old/sounddevice-0.3.15/examples/play_stream.py 1970-01-01 01:00:00.000000000 +0100 +++ new/sounddevice-0.4.0/examples/play_stream.py 2020-07-15 17:23:35.000000000 +0200 @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +"""Play a web stream. + +ffmpeg-python (https://github.com/kkroening/ffmpeg-python) has to be installed. + +If you don't know a stream URL, try http://icecast.spc.org:8000/longplayer +(see https://longplayer.org/ for a description). + +""" +import argparse +import queue +import sys + +import ffmpeg +import sounddevice as sd + + +def int_or_str(text): + """Helper function for argument parsing.""" + try: + return int(text) + except ValueError: + return text + + +parser = argparse.ArgumentParser(add_help=False) +parser.add_argument( + '-l', '--list-devices', action='store_true', + help='show list of audio devices and exit') +args, remaining = parser.parse_known_args() +if args.list_devices: + print(sd.query_devices()) + parser.exit(0) +parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + parents=[parser]) +parser.add_argument( + 'url', metavar='URL', + help='stream URL') +parser.add_argument( + '-d', '--device', type=int_or_str, + help='output device (numeric ID or substring)') +parser.add_argument( + '-b', '--blocksize', type=int, default=1024, + help='block size (default: %(default)s)') +parser.add_argument( + '-q', '--buffersize', type=int, default=20, + help='number of blocks used for buffering (default: %(default)s)') +args = parser.parse_args(remaining) +if args.blocksize == 0: + parser.error('blocksize must not be zero') +if args.buffersize < 1: + parser.error('buffersize must be at least 1') + +q = queue.Queue(maxsize=args.buffersize) + +print('Getting stream information ...') + +try: + info = ffmpeg.probe(args.url) +except ffmpeg.Error as e: + sys.stderr.buffer.write(e.stderr) + parser.exit(e) + +streams = info.get('streams', []) +if len(streams) != 1: + parser.exit('There must be exactly one stream available') + +stream = streams[0] + +if stream.get('codec_type') != 'audio': + parser.exit('The stream must be an audio stream') + +channels = stream['channels'] +samplerate = float(stream['sample_rate']) + + +def callback(outdata, frames, time, status): + assert frames == args.blocksize + if status.output_underflow: + print('Output underflow: increase blocksize?', file=sys.stderr) + raise sd.CallbackAbort + assert not status + try: + data = q.get_nowait() + except queue.Empty: + print('Buffer is empty: increase buffersize?', file=sys.stderr) + raise sd.CallbackAbort + assert len(data) == len(outdata) + outdata[:] = data + + +try: + print('Opening stream ...') + process = ffmpeg.input( + args.url + ).output( + 'pipe:', + format='f32le', + acodec='pcm_f32le', + ac=channels, + ar=samplerate, + loglevel='quiet', + ).run_async(pipe_stdout=True) + stream = sd.RawOutputStream( + samplerate=samplerate, blocksize=args.blocksize, + device=args.device, channels=channels, dtype='float32', + callback=callback) + read_size = args.blocksize * channels * stream.samplesize + print('Buffering ...') + for _ in range(args.buffersize): + q.put_nowait(process.stdout.read(read_size)) + print('Starting Playback ...') + with stream: + timeout = args.blocksize * args.buffersize / samplerate + while True: + q.put(process.stdout.read(read_size), timeout=timeout) +except KeyboardInterrupt: + parser.exit('\nInterrupted by user') +except queue.Full: + # A timeout occurred, i.e. there was an error in the callback + parser.exit(1) +except Exception as e: + parser.exit(type(e).__name__ + ': ' + str(e)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/setup.py new/sounddevice-0.4.0/setup.py --- old/sounddevice-0.3.15/setup.py 2020-03-18 09:54:19.000000000 +0100 +++ new/sounddevice-0.4.0/setup.py 2020-07-15 17:23:35.000000000 +0200 @@ -10,9 +10,7 @@ break PYTHON_INTERPRETERS = '.'.join([ - 'cp26', 'cp27', 'cp32', 'cp33', 'cp34', 'cp35', 'cp36', 'cp37', 'cp38', 'cp39', - 'pp27', 'pp32', 'pp33', 'pp34', 'pp35', 'pp36', 'pp37', ]) MACOSX_VERSIONS = '.'.join([ @@ -50,7 +48,7 @@ """Create OS-dependent, but Python-independent wheels.""" def get_tag(self): - pythons = 'py2.py3.' + PYTHON_INTERPRETERS + pythons = 'py3.' + PYTHON_INTERPRETERS if system == 'Darwin': oses = MACOSX_VERSIONS elif system == 'Windows': @@ -59,7 +57,7 @@ else: oses = 'win_amd64' else: - pythons = 'py2.py3' + pythons = 'py3' oses = 'any' return pythons, 'none', oses @@ -72,7 +70,7 @@ packages=packages, package_data=package_data, zip_safe=zip_safe, - python_requires='>=2.6', + python_requires='>=3', setup_requires=['CFFI>=1.0'], install_requires=['CFFI>=1.0'], extras_require={'NumPy': ['NumPy']}, @@ -89,7 +87,6 @@ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Topic :: Multimedia :: Sound/Audio', ], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/sounddevice.egg-info/PKG-INFO new/sounddevice-0.4.0/sounddevice.egg-info/PKG-INFO --- old/sounddevice-0.3.15/sounddevice.egg-info/PKG-INFO 2020-03-18 10:14:15.000000000 +0100 +++ new/sounddevice-0.4.0/sounddevice.egg-info/PKG-INFO 2020-07-18 15:54:17.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: sounddevice -Version: 0.3.15 +Version: 0.4.0 Summary: Play and Record Sound with Python Home-page: http://python-sounddevice.readthedocs.io/ Author: Matthias Geier @@ -32,8 +32,7 @@ Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Multimedia :: Sound/Audio -Requires-Python: >=2.6 +Requires-Python: >=3 Provides-Extra: NumPy diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/sounddevice.egg-info/SOURCES.txt new/sounddevice-0.4.0/sounddevice.egg-info/SOURCES.txt --- old/sounddevice-0.3.15/sounddevice.egg-info/SOURCES.txt 2020-03-18 10:14:15.000000000 +0100 +++ new/sounddevice-0.4.0/sounddevice.egg-info/SOURCES.txt 2020-07-18 15:54:17.000000000 +0200 @@ -18,6 +18,7 @@ doc/version-history.rst doc/api/checking-hardware.rst doc/api/convenience-functions.rst +doc/api/expert-mode.rst doc/api/index.rst doc/api/misc.rst doc/api/module-defaults.rst @@ -29,6 +30,7 @@ examples/play_file.py examples/play_long_file.py examples/play_sine.py +examples/play_stream.py examples/plot_input.py examples/priming_output.py examples/rec_gui.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sounddevice-0.3.15/sounddevice.py new/sounddevice-0.4.0/sounddevice.py --- old/sounddevice-0.3.15/sounddevice.py 2020-03-18 10:10:01.000000000 +0100 +++ new/sounddevice-0.4.0/sounddevice.py 2020-07-18 15:51:44.000000000 +0200 @@ -48,7 +48,7 @@ https://python-sounddevice.readthedocs.io/ """ -__version__ = '0.3.15' +__version__ = '0.4.0' import atexit as _atexit import os as _os @@ -108,7 +108,7 @@ scripts. It cannot be used for multiple overlapping playbacks. This function does the following steps internally: - + * Call `stop()` to terminate any currently running invocation of `play()`, `rec()` and `playrec()`. @@ -190,7 +190,7 @@ scripts. This function does the following steps internally: - + * Call `stop()` to terminate any currently running invocation of `play()`, `rec()` and `playrec()`. @@ -261,14 +261,14 @@ argument or by assigning a value to `default.samplerate`), the default sampling rate of the sound device will be used (see `query_devices()`). - + See Also -------- play, playrec """ ctx = _CallbackContext() - ctx.frames = ctx.check_out(out, frames, channels, dtype, mapping) + out, ctx.frames = ctx.check_out(out, frames, channels, dtype, mapping) def callback(indata, frames, time, status): assert len(indata) == frames @@ -278,7 +278,7 @@ ctx.start_stream(InputStream, samplerate, ctx.input_channels, ctx.input_dtype, callback, blocking, **kwargs) - return ctx.out + return out def playrec(data, samplerate=None, channels=None, dtype=None, @@ -287,7 +287,7 @@ """Simultaneous playback and recording of NumPy arrays. This function does the following steps internally: - + * Call `stop()` to terminate any currently running invocation of `play()`, `rec()` and `playrec()`. @@ -355,8 +355,8 @@ output_frames = ctx.check_data(data, output_mapping, kwargs.get('device')) if dtype is None: dtype = ctx.data.dtype # ignore module defaults - input_frames = ctx.check_out(out, output_frames, channels, dtype, - input_mapping) + out, input_frames = ctx.check_out(out, output_frames, channels, dtype, + input_mapping) if input_frames != output_frames: raise ValueError('len(data) != len(out)') ctx.frames = input_frames @@ -374,7 +374,7 @@ callback, blocking, prime_output_buffers_using_stream_callback=False, **kwargs) - return ctx.out + return out def wait(ignore_errors=True): @@ -559,14 +559,14 @@ """ if kind not in ('input', 'output', None): - raise ValueError('Invalid kind: {0!r}'.format(kind)) + raise ValueError('Invalid kind: {!r}'.format(kind)) if device is None and kind is None: return DeviceList(query_devices(i) for i in range(_check(_lib.Pa_GetDeviceCount()))) device = _get_device_id(device, kind, raise_on_error=True) info = _lib.Pa_GetDeviceInfo(device) if not info: - raise PortAudioError('Error querying device {0}'.format(device)) + raise PortAudioError('Error querying device {}'.format(device)) assert info.structVersion == 2 name_bytes = _ffi.string(info.name) try: @@ -596,7 +596,7 @@ } if kind and device_dict['max_' + kind + '_channels'] < 1: raise ValueError( - 'Not an {0} device: {1!r}'.format(kind, device_dict['name'])) + 'Not an {} device: {!r}'.format(kind, device_dict['name'])) return device_dict @@ -642,7 +642,7 @@ for i in range(_check(_lib.Pa_GetHostApiCount()))) info = _lib.Pa_GetHostApiInfo(index) if not info: - raise PortAudioError('Error querying host API {0}'.format(index)) + raise PortAudioError('Error querying host API {}'.format(index)) assert info.structVersion == 1 return { 'name': _ffi.string(info.name).decode(), @@ -727,6 +727,45 @@ dither_off=None, never_drop_input=None, prime_output_buffers_using_stream_callback=None, userdata=None, wrap_callback=None): + """Base class for PortAudio streams. + + This class should only be used by library authors who want to + create their own custom stream classes. + Most users should use the derived classes + `Stream`, `InputStream`, `OutputStream`, + `RawStream`, `RawInputStream` and `RawOutputStream` instead. + + This class has the same properties and methods as `Stream`, + except for `read_available`/:meth:`~Stream.read` and + `write_available`/:meth:`~Stream.write`. + + It can be created with the same parameters as `Stream`, + except that there are three additional parameters + and the *callback* parameter also accepts a C function pointer. + + Parameters + ---------- + kind : {'input', 'output', 'duplex'} + The desired type of stream: for recording, playback or both. + callback : Python callable or CData function pointer, optional + If *wrap_callback* is ``None`` this can be a function pointer + provided by CFFI. + Otherwise, it has to be a Python callable. + wrap_callback : {'array', 'buffer'}, optional + If *callback* is a Python callable, this selects whether + the audio data is provided as NumPy array (like in `Stream`) + or as Python buffer object (like in `RawStream`). + userdata : CData void pointer + This is passed to the underlying C callback function + on each call and can only be accessed from a *callback* + provided as ``CData`` function pointer. + + Examples + -------- + A usage example of this class can be seen at + https://github.com/spatialaudio/python-rtmixer. + + """ assert kind in ('input', 'output', 'duplex') assert wrap_callback in ('array', 'buffer', None) if blocksize is None: @@ -858,7 +897,7 @@ _check(_lib.Pa_OpenStream(self._ptr, iparameters, oparameters, samplerate, blocksize, stream_flags, callback_ptr, userdata), - 'Error opening {0}'.format(self.__class__.__name__)) + 'Error opening {}'.format(self.__class__.__name__)) # dereference PaStream** --> PaStream* self._ptr = self._ptr[0] @@ -1493,7 +1532,7 @@ if data.ndim > 1 and data.shape[1] != channels: raise ValueError('Number of channels must match') if data.dtype != dtype: - raise TypeError('dtype mismatch: {0!r} vs {1!r}'.format( + raise TypeError('dtype mismatch: {!r} vs {!r}'.format( data.dtype.name, dtype)) if not data.flags.c_contiguous: raise TypeError('data must be C-contiguous') @@ -1776,14 +1815,18 @@ ins=info['max_input_channels'], outs=info['max_output_channels']) for idx, info in enumerate(self)) - if _sys.version_info.major < 3: - return text.encode(_sys.stdout.encoding or 'utf-8', 'replace') return text class CallbackFlags(object): """Flag bits for the *status* argument to a stream *callback*. + If you experience under-/overflows, you can try to increase the + ``latency`` and/or ``blocksize`` settings. + You should also avoid anything that could block the callback + function for a long time, e.g. extensive computations, waiting for + another thread, reading/writing files, network connections, etc. + See Also -------- Stream @@ -1812,7 +1855,7 @@ flags = str(self) if not flags: flags = 'no flags set' - return '<sounddevice.CallbackFlags: {0}>'.format(flags) + return '<sounddevice.CallbackFlags: {}>'.format(flags) def __str__(self): return ', '.join(name.replace('_', ' ') for name in dir(self) @@ -1821,8 +1864,6 @@ def __bool__(self): return bool(self._flags) - __nonzero__ = __bool__ # For Python 2.x - def __ior__(self, other): if not isinstance(other, CallbackFlags): return NotImplemented @@ -1852,8 +1893,9 @@ In a stream opened with ``blocksize=0``, indicates that data prior to the first sample of the input buffer was discarded due to an overflow, possibly because the stream callback is using - too much CPU time. Otherwise indicates that data prior to one - or more samples in the input buffer was discarded. + too much CPU time. In a stream opened with a non-zero + *blocksize*, it indicates that data prior to one or more samples + in the input buffer was discarded. This can happen in full-duplex and input-only streams (including `playrec()` and `rec()`). @@ -2141,11 +2183,11 @@ def __str__(self): errormsg = self.args[0] if self.args else '' if len(self.args) > 1: - errormsg = '{0} [PaErrorCode {1}]'.format(errormsg, self.args[1]) + errormsg = '{} [PaErrorCode {}]'.format(errormsg, self.args[1]) if len(self.args) > 2: host_api, hosterror_code, hosterror_text = self.args[2] hostname = query_hostapis(host_api)['name'] - errormsg = "{0}: '{1}' [{2} error {3}]".format( + errormsg = "{}: '{}' [{} error {}]".format( errormsg, hosterror_text, hostname, hosterror_code) return errormsg @@ -2367,6 +2409,7 @@ blocksize = None data = None + out = None frame = 0 input_channels = output_channels = None input_dtype = output_dtype = None @@ -2448,7 +2491,7 @@ self.input_channels = channels self.input_dtype = dtype self.input_mapping = mapping - return frames + return out, frames def callback_enter(self, status, data): """Check status and blocksize.""" @@ -2486,6 +2529,12 @@ def finished_callback(self): self.event.set() + # Drop temporary audio buffers to free memory + self.data = None + self.out = None + # Drop CFFI objects to avoid reference cycles + self.stream._callback = None + self.stream._finished_callback = None def start_stream(self, StreamClass, samplerate, channels, dtype, callback, blocking, **kwargs): @@ -2617,7 +2666,12 @@ def _split(value): - """Split input/output value into two values.""" + """Split input/output value into two values. + + This can be useful for generic code that allows using the same value + for input and output but also a pair of two separate values. + + """ if isinstance(value, _basestring): # iterable, but not meant for splitting return value, value @@ -2637,7 +2691,7 @@ errormsg = _ffi.string(_lib.Pa_GetErrorText(err)).decode() if msg: - errormsg = '{0}: {1}'.format(msg, errormsg) + errormsg = '{}: {}'.format(msg, errormsg) if err == _lib.paUnanticipatedHostError: # (gh82) We grab the host error info here rather than inside @@ -2669,7 +2723,7 @@ if idev == odev: id_or_query_string = idev else: - raise ValueError('Input and output device are different: {0!r}' + raise ValueError('Input and output device are different: {!r}' .format(id_or_query_string)) if isinstance(id_or_query_string, int): @@ -2712,7 +2766,7 @@ if raise_on_error: raise ValueError('Multiple ' + kind + ' devices found for ' + repr(id_or_query_string) + ':\n' + - '\n'.join('[{0}] {1}'.format(id, name) + '\n'.join('[{}] {}'.format(id, name) for id, name in matches)) else: return -1 @@ -2722,9 +2776,12 @@ def _initialize(): """Initialize PortAudio. - This temporarily forwards messages from stderr to /dev/null + This temporarily forwards messages from stderr to ``/dev/null`` (where supported). + In most cases, this doesn't have to be called explicitly, because it + is automatically called with the ``import sounddevice`` statement. + """ old_stderr = None try: @@ -2752,7 +2809,11 @@ def _terminate(): - """Terminate PortAudio.""" + """Terminate PortAudio. + + In most cases, this doesn't have to be called explicitly. + + """ global _initialized _check(_lib.Pa_Terminate(), 'Error terminating PortAudio') _initialized -= 1
