diff --git a/.gitignore b/.gitignore index 1312ce1..40936db 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build/ dist/ docs/ MANIFEST +*.sublime-workspace diff --git a/CHANGELOG b/CHANGELOG index 3eeb31e..56f0d5a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,50 @@ +2017-03-18 Hubert Pham + + PyAudio 0.2.11 + + - Fix use-after-free memory issue in callback handler. + + Thanks to both Blaise Potard and Matthias Schaff for their patches! + + - Fix docstring for get_output_latency(). + + Thanks to Timothy Port for finding the issue! + +2017-01-10 Hubert Pham + + PyAudio 0.2.10 + + - Release the GIL during PortAudio I/O calls to avoid potential deadlock. + + Thanks to Michael Graczyk for submitting a patch! + + - Add a few automated unit tests. + +2015-10-18 Hubert Pham + + PyAudio 0.2.9 + + - Fix overflow error handling logic for pa_read_stream. + + Stream.read takes an additional parameter that specifies whether + an exception is raised on audio buffer overflow, for parity with + Stream.write. Includes relevant bug fixes in the C module logic. + + Thanks to Tony Jacobson for submitting a patch! + + - Fix IOError arguments. + + IOError exceptions previously had values in the strerror and errno fields + swapped, which is now corrected. + + Thanks to Sami Liedes for the report! + + - Miscellaneous updates. + + Python library surfaces issues with importing low-level C module. + Code formatting update. + Updates to examples for Python 3 compatibility. + 2014-02-16 Hubert Pham PyAudio 0.2.8 @@ -96,4 +143,3 @@ 2008-02-12 Justin Mazzola Paluska - Initial version of debian packaging. - diff --git a/INSTALL b/INSTALL index 3dba81c..68bba39 100644 --- a/INSTALL +++ b/INSTALL @@ -8,81 +8,83 @@ platforms: * General UNIX Guide: (GNU/Linux, Mac OS X, Cygwin) * Microsoft Windows (native) -Generally speaking, you must first install the PortAudio v19 library -before building PyAudio. - +Generally speaking, installation involves building the PortAudio v19 +library and then building PyAudio. ---------------------------------------------------------------------- General UNIX Guide (GNU/Linux, Mac OS X, Cygwin) ---------------------------------------------------------------------- -1. Build and install PortAudio, i.e.: +1. Use a package manager to install PortAudio v19. + + To build PortAudio from source instead, extract the source and run: % ./configure % make % make install # you may need to be root - (Or better yet, use your package manager to install PortAudio v19) - -2. Extract PyAudio; to build and install, run: +2. Extract PyAudio. To build and install, run: % python setup.py install - ---------------------------------------------------------------------- Microsoft Windows ---------------------------------------------------------------------- -If you are targeting native Win32 Python, you will need either -Microsoft Visual Studio or MinGW (via Cygwin). Here are compilation -hints for using MinGW under the Cygwin build environment. +Targeting native Win32 Python will require either Microsoft Visual +Studio or MinGW (via Cygwin). Here are compilation hints for using +MinGW under the Cygwin build environment. Note: I've only tested this under Cygwin's build environment. Your mileage may vary in other environments (i.e., compiling PortAudio with -MinGW's compiler and environment). +MinGW's compiler). -(If you have instructions for building PyAudio using Visual Studio, -I'd love to hear about it.) +1. Install cygwin's gcc and mingw packages. -1. Download PortAudio to ./portaudio-v19 in this directory - and build. When running configure, be sure to use ``-mno-cygwin`` - (under cygwin) to generate native Win32 binaries: +2. Download PortAudio and build. When running configure, be sure to + specify the MinGW compiler (via a CC environment variable) to + generate native Win32 binaries: - % cd ./portaudio-v19 - % CFLAGS="-mno-cygwin" LDFLAGS="-mno-cygwin" ./configure - % make - % cd .. + % CC=i686-w64-mingw32-gcc ./configure --enable-static --with-pic + % make -2. To build PyAudio, run (from this directory): +3. Before building PyAudio, apply a few necessary modifications: - % python setup.py build --static-link -cmingw32 + a. Python distutils calls ``gcc'' to build the C extension, so + temporarily move your MinGW compiler to /usr/bin/gcc. - Be sure to invoke the native Win32 python rather than cygwin's - python. The --static-link option statically links in the PortAudio - library to the PyAudio module, which is probably the best way to go - on Windows. + b. Modify Python's Lib/distutils/cygwincompiler.py so that + mscvr900.dll is not included in the build. See: + http://bugs.python.org/issue16472. - From: http://boodebr.org/main/python/build-windows-extensions + Both Python 2.7 and Python 3+ require similar modification. - Update: 2008-09-10 + c. For some versions of Python (e.g., Python 2.7 32-bit), it is + necessary to further modify Python's + Lib/distutils/cygwincompiler.py and remove references to + -cmingw32, a flag which is no longer supported. + See http://hg.python.org/cpython/rev/6b89176f1be5/. - Recent versions of Cygwin binutils have version numbers that are - breaking the version number parsing, resulting in errors like: - ValueError: invalid version number '2.18.50.20080625' + d. For some versions of 64-bit Python 3 (e.g., Python 3.2, 3.3, 3.4), + it is necessary to generate .a archive of the Python DLL. + See https://bugs.python.org/issue20785. Example for Python 3.4: - To fix this, edit distutils/version.py. At line 100, replace: + % cd /path/to/Python34-x64/libs/ + % gendef /path/to/Windows/System32/python34.dll + % dlltool --as-flags=--64 -m i386:x64-64 -k --output-lib libpython34.a \ + --input-def python34.def - version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', - re.VERBOSE) +4. To build PyAudio, run: - with + % PORTAUDIO_PATH=/path/to/portaudio_tree /path/to/win/python \ + setup.py build --static-link -cmingw32 - version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? (\. (\d+))?$', - re.VERBOSE) + Be sure to invoke the native Win32 python rather than cygwin's + python. The --static-link option statically links in the PortAudio + library to the PyAudio module. -3. To install PyAudio: +5. To install PyAudio: - % python setup.py install --skip-build + % python setup.py install --skip-build - The --skip-build option prevents Python from searching your system - for Visual Studio and the .NET framework. + Or create a Python wheel and install using pip. diff --git a/MANIFEST.in b/MANIFEST.in index a19b43a..8214bd0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include src/*.c src/*.h src/*.py include Makefile CHANGELOG INSTALL MANIFEST.in -recursive-include test *.py *.c -graft docs +recursive-include examples *.py +recursive-include tests *.py graft sphinx diff --git a/Makefile b/Makefile index a6bd05c..c7c14a0 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ .PHONY: docs clean build -VERSION := 0.2.8 +VERSION := 0.2.11 PYTHON ?= python BUILD_ARGS ?= SPHINX ?= sphinx-build @@ -11,7 +11,8 @@ PYTHON_BUILD_DIR:=$(shell $(PYTHON) -c "import distutils.util; import sys; print BUILD_DIR:=lib.$(PYTHON_BUILD_DIR) BUILD_STAMP:=$(BUILD_DIR)/build SRCFILES := src/*.c src/*.h src/*.py -EXAMPLES := test/*.py +EXAMPLES := examples/*.py +TESTS := tests/*.py what: @echo "make targets:" @@ -43,5 +44,5 @@ docs: build ###################################################################### # Source Tarball ###################################################################### -tarball: docs $(SRCFILES) $(EXAMPLES) MANIFEST.in +tarball: $(SRCFILES) $(EXAMPLES) $(TESTS) MANIFEST.in @$(PYTHON) setup.py sdist diff --git a/PyAudioAndPortaudio.sublime-project b/PyAudioAndPortaudio.sublime-project new file mode 100644 index 0000000..0c4f9f4 --- /dev/null +++ b/PyAudioAndPortaudio.sublime-project @@ -0,0 +1,11 @@ +{ + "folders": + [ + { + "path": "../portaudio" + }, + { + "path": "." + } + ] +} diff --git a/README b/README index 661854b..a0b4ca3 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ ====================================================================== -PyAudio v0.2.8: Python Bindings for PortAudio. +PyAudio v0.2.11: Python Bindings for PortAudio. ====================================================================== See: http://people.csail.mit.edu/hubert/pyaudio/ @@ -8,13 +8,24 @@ PyAudio provides Python bindings for PortAudio v19, the cross-platform audio I/O library. Using PyAudio, you can easily use Python to play and record audio on a variety of platforms. -See INSTALL for compilation hints. +## Installation (Visual Studio) + +1. Download and install Visual Studio 2017 or superior and: +1. `git clone https://github.com/evandroforks/portaudio` +1. Open the file `portaudio\build\msvc\portaudio.sln` with Visual Studio +1. Select the Solution Configuration as `Release` and the Solution Platforms as `x64` on the top toolbar +1. Go to the menu `Build -> Build Solution` +1. Copy the file `portaudio\build\msvc\x64\Release\portaudio.lib` to `C:\Python\libs\` (Or whatever your Windows Python is installed) +1. Run the command: `python -m pip install git+https://github.com/evandroforks/pyaudio` +1. Or just clone this repository and run `cd pyaudio && python -m pip install .` + +See INSTALL for other compilation hints. ====================================================================== PyAudio : Python Bindings for PortAudio. -Copyright (c) 2006-2014 Hubert Pham +Copyright (c) 2006 Hubert Pham Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -36,4 +47,3 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ====================================================================== - diff --git a/test/error.py b/examples/error.py similarity index 100% rename from test/error.py rename to examples/error.py diff --git a/test/play_wave.py b/examples/play_wave.py similarity index 97% rename from test/play_wave.py rename to examples/play_wave.py index 9c6c36f..19c036d 100644 --- a/test/play_wave.py +++ b/examples/play_wave.py @@ -25,7 +25,7 @@ data = wf.readframes(CHUNK) # play stream (3) -while data != '': +while len(data) > 0: stream.write(data) data = wf.readframes(CHUNK) diff --git a/test/play_wave_callback.py b/examples/play_wave_callback.py similarity index 100% rename from test/play_wave_callback.py rename to examples/play_wave_callback.py diff --git a/test/play_wave_macosx_channelmap.py b/examples/play_wave_macosx_channelmap.py similarity index 98% rename from test/play_wave_macosx_channelmap.py rename to examples/play_wave_macosx_channelmap.py index ea0816a..3fbdb8b 100644 --- a/test/play_wave_macosx_channelmap.py +++ b/examples/play_wave_macosx_channelmap.py @@ -64,7 +64,7 @@ data = wf.readframes(chunk) # play stream -while data != '': +while len(data) > 0: stream.write(data) data = wf.readframes(chunk) diff --git a/test/record.py b/examples/record.py similarity index 100% rename from test/record.py rename to examples/record.py diff --git a/test/system_info.py b/examples/system_info.py similarity index 100% rename from test/system_info.py rename to examples/system_info.py diff --git a/test/wire_callback.py b/examples/wire_callback.py similarity index 88% rename from test/wire_callback.py rename to examples/wire_callback.py index f214bf9..10d1ac4 100644 --- a/test/wire_callback.py +++ b/examples/wire_callback.py @@ -12,6 +12,7 @@ WIDTH = 2 CHANNELS = 2 RATE = 44100 +DURATION = 5 if sys.platform == 'darwin': CHANNELS = 1 @@ -30,7 +31,9 @@ def callback(in_data, frame_count, time_info, status): stream.start_stream() -while stream.is_active(): +start = time.time() + +while stream.is_active() and (time.time() - start) < DURATION: time.sleep(0.1) stream.stop_stream() diff --git a/test/wire_full.py b/examples/wire_full.py similarity index 100% rename from test/wire_full.py rename to examples/wire_full.py diff --git a/test/wire_half.py b/examples/wire_half.py similarity index 100% rename from test/wire_half.py rename to examples/wire_half.py diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 6bcb06a..e23d085 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ """ -PyAudio v0.2.8: Python Bindings for PortAudio. +PyAudio v0.2.11: Python Bindings for PortAudio. -Copyright (c) 2006-2014 Hubert Pham +Copyright (c) 2006 Hubert Pham Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -26,25 +26,29 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from distutils.core import setup, Extension -import sys import os +import platform +import sys -__version__ = "0.2.8" +try: + from setuptools import setup, Extension + from setuptools.command.build_ext import build_ext -# Note: distutils will try to locate and link dynamically -# against portaudio. -# -# You probably don't want to statically link in the PortAudio -# library unless you're building on Microsoft Windows. +except ImportError: + from distutils.core import setup, Extension + from distutils.command.build_ext import build_ext + +__version__ = "0.2.11" + +# distutils will try to locate and link dynamically against portaudio. # -# In any case, if you would rather statically link in libportaudio, -# run: +# If you would rather statically link in the portaudio library (e.g., +# typically on Microsoft Windows), run: # -# % python setup.py build --static-link +# % python setup.py build --static-link # -# Be sure to specify the location of the libportaudio.a in -# the `extra_link_args' variable below. +# Specify the environment variable PORTAUDIO_PATH with the build tree +# of PortAudio. STATIC_LINKING = False @@ -56,7 +60,6 @@ mac_sysroot_path = os.environ.get("SYSROOT_PATH", None) pyaudio_module_sources = ['src/_portaudiomodule.c'] - include_dirs = [] external_libraries = [] extra_compile_args = [] @@ -64,24 +67,25 @@ scripts = [] defines = [] -if STATIC_LINKING: - extra_link_args = [ - os.path.join(portaudio_path, 'lib/.libs/libportaudio.a') - ] - include_dirs = [os.path.join(portaudio_path, 'include/')] -else: - # dynamic linking - external_libraries = ['portaudio'] - extra_link_args = [] - if sys.platform == 'darwin': defines += [('MACOSX', '1')] - if mac_sysroot_path: extra_compile_args += ["-isysroot", mac_sysroot_path] extra_link_args += ["-isysroot", mac_sysroot_path] +elif sys.platform == 'win32': + bits = platform.architecture()[0] + if '64' in bits: + defines.append(('MS_WIN64', '1')) -if STATIC_LINKING: +if not STATIC_LINKING: + extra_link_args = [] + external_libraries = ['portaudio'] + +else: + include_dirs = [os.path.join(portaudio_path, 'include/')] + extra_link_args = [ + os.path.join(portaudio_path, 'lib/.libs/libportaudio.a') + ] # platform specific configuration if sys.platform == 'darwin': @@ -89,45 +93,69 @@ '-framework', 'AudioToolbox', '-framework', 'AudioUnit', '-framework', 'Carbon'] - elif sys.platform == 'cygwin': external_libraries += ['winmm'] extra_link_args += ['-lwinmm'] - elif sys.platform == 'win32': # i.e., Win32 Python with mingw32 # run: python setup.py build -cmingw32 external_libraries += ['winmm'] extra_link_args += ['-lwinmm'] - elif sys.platform == 'linux2': extra_link_args += ['-lrt', '-lm', '-lpthread'] - - # Since you're insisting on linking statically against - # PortAudio on GNU/Linux, be sure to link in whatever sound - # backend you used in portaudio (e.g., ALSA, JACK, etc...) - - # I'll start you off with ALSA, since that's the most common - # today. If you need JACK support, add it here. - + # GNU/Linux has several audio systems (backends) available; be + # sure to specify the desired ones here. Start with ALSA and + # JACK, since that's common today. extra_link_args += ['-lasound', '-ljack'] -pyaudio = Extension('_portaudio', - sources=pyaudio_module_sources, - include_dirs=include_dirs, - define_macros=defines, - libraries=external_libraries, - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args) - -setup(name = 'PyAudio', - version = __version__, - author = "Hubert Pham", - url = "http://people.csail.mit.edu/hubert/pyaudio/", - description = 'PortAudio Python Bindings', - long_description = __doc__.lstrip(), - scripts = scripts, - py_modules = ['pyaudio'], - package_dir = {'': 'src'}, - ext_modules = [pyaudio]) +myextension = Extension( + '_portaudio', + sources=pyaudio_module_sources, + include_dirs=include_dirs, + define_macros=defines, + libraries=external_libraries, + extra_compile_args=extra_compile_args, + extra_link_args=extra_link_args +) + +class build_ext_compiler_check(build_ext): + def build_extensions(self): + compiler = self.compiler.compiler_type + + # print('\n\ncompiler', compiler, 'debug_variable_value', debug_variable_value) + for extension in self.extensions: + + # https://stackoverflow.com/questions/22954119/linker-error-while-linking-some-windows-apis + # https://docs.microsoft.com/en-us/cpp/error-messages/tool-errors/linker-tools-warning-lnk4098?view=vs-2019 + if extension == myextension: + + if 'msvc' in compiler: + # extension.extra_link_args.append('/VERBOSE:LIB') + extension.extra_link_args.append('/NODEFAULTLIB:libcmt.lib') + extension.extra_link_args.append('/DEFAULTLIB:advapi32.lib') + + # else: + # extension.extra_compile_args.append( '-ggdb' ) + # extension.extra_link_args.append( '-std=c++11' ) + # extension.libraries.append( 'hs' ) + # extension.include_dirs.append( '/usr/include/hs' ) + + super().build_extensions() + +cmdclass = {} +cmdclass['build_ext'] = build_ext_compiler_check + + +setup(name='PyAudio', + version=__version__, + author="Hubert Pham", + url="http://people.csail.mit.edu/hubert/pyaudio/", + description='PortAudio Python Bindings', + long_description=__doc__.lstrip(), + scripts=scripts, + cmdclass=cmdclass, + py_modules=['pyaudio'], + package_dir={'': 'src'}, + ext_modules=[myextension] + ) diff --git a/sphinx/conf.py b/sphinx/conf.py index e240ad9..240a54d 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py @@ -42,16 +42,16 @@ # General information about the project. project = 'PyAudio' -copyright = '2014, Hubert Pham' +copyright = '2006, Hubert Pham' # 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.2.8' +version = '0.2.11' # The full version, including alpha/beta/rc tags. -release = '0.2.8' +release = '0.2.11' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/sphinx/examples.rst b/sphinx/examples.rst index 14bf684..98fda70 100644 --- a/sphinx/examples.rst +++ b/sphinx/examples.rst @@ -1,7 +1,7 @@ Example: Blocking Mode Audio I/O -------------------------------- -.. literalinclude:: ../test/play_wave.py +.. literalinclude:: ../examples/play_wave.py To use PyAudio, first instantiate PyAudio using :py:func:`pyaudio.PyAudio` (1), which sets up the portaudio system. @@ -30,7 +30,7 @@ Finally, terminate the portaudio session using Example: Callback Mode Audio I/O -------------------------------- -.. literalinclude:: ../test/play_wave_callback.py +.. literalinclude:: ../examples/play_wave_callback.py In callback mode, PyAudio will call a specified callback function (2) whenever it needs new audio data (to play) and/or when there is new diff --git a/src/_portaudiomodule.c b/src/_portaudiomodule.c index 9eae644..3650cb2 100644 --- a/src/_portaudiomodule.c +++ b/src/_portaudiomodule.c @@ -1,7 +1,7 @@ /** * PyAudio: Python Bindings for PortAudio. * - * Copyright (c) 2006-2012 Hubert Pham + * Copyright (c) 2006 Hubert Pham * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -36,10 +36,7 @@ #define DEFAULT_FRAMES_PER_BUFFER 1024 /* #define VERBOSE */ -#define min(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a < _b ? _a : _b; }) +#define ADDRESS_MIN(a, b) ((a) < (b) ? (a) : (b)) /************************************************************ * @@ -62,7 +59,6 @@ * ************************************************************/ - /************************************************************ * * I. Exportable Python Methods @@ -70,86 +66,79 @@ ************************************************************/ static PyMethodDef paMethods[] = { - - /* version */ - {"get_version", pa_get_version, METH_VARARGS, "get version"}, - {"get_version_text", pa_get_version_text, METH_VARARGS, - "get version text"}, - - /* inits */ - {"initialize", pa_initialize, METH_VARARGS, "initialize portaudio"}, - {"terminate", pa_terminate, METH_VARARGS, "terminate portaudio"}, - - /* host api */ - {"get_host_api_count", pa_get_host_api_count, METH_VARARGS, - "get host API count"}, - - {"get_default_host_api", pa_get_default_host_api, METH_VARARGS, - "get default host API index"}, - - {"host_api_type_id_to_host_api_index", - pa_host_api_type_id_to_host_api_index, METH_VARARGS, - "get default host API index"}, - - {"host_api_device_index_to_device_index", - pa_host_api_device_index_to_device_index, - METH_VARARGS, - "get default host API index"}, - - {"get_host_api_info", pa_get_host_api_info, METH_VARARGS, - "get host api information"}, - - /* device api */ - {"get_device_count", pa_get_device_count, METH_VARARGS, - "get host API count"}, - - {"get_default_input_device", pa_get_default_input_device, METH_VARARGS, - "get default input device index"}, - - {"get_default_output_device", pa_get_default_output_device, METH_VARARGS, - "get default output device index"}, - - {"get_device_info", pa_get_device_info, METH_VARARGS, - "get device information"}, - - /* stream open/close */ - {"open", (PyCFunction) pa_open, METH_VARARGS | METH_KEYWORDS, - "open port audio stream"}, - {"close", pa_close, METH_VARARGS, "close port audio stream"}, - {"get_sample_size", pa_get_sample_size, METH_VARARGS, - "get sample size of a format in bytes"}, - {"is_format_supported", (PyCFunction) pa_is_format_supported, - METH_VARARGS | METH_KEYWORDS, - "returns whether specified format is supported"}, - - /* stream start/stop */ - {"start_stream", pa_start_stream, METH_VARARGS, "starts port audio stream"}, - {"stop_stream", pa_stop_stream, METH_VARARGS, "stops port audio stream"}, - {"abort_stream", pa_abort_stream, METH_VARARGS, "aborts port audio stream"}, - {"is_stream_stopped", pa_is_stream_stopped, METH_VARARGS, - "returns whether stream is stopped"}, - {"is_stream_active", pa_is_stream_active, METH_VARARGS, - "returns whether stream is active"}, - {"get_stream_time", pa_get_stream_time, METH_VARARGS, - "returns stream time"}, - {"get_stream_cpu_load", pa_get_stream_cpu_load, METH_VARARGS, - "returns stream CPU load -- always 0 for blocking mode"}, - - /* stream read/write */ - {"write_stream", pa_write_stream, METH_VARARGS, "write to stream"}, - {"read_stream", pa_read_stream, METH_VARARGS, "read from stream"}, - - {"get_stream_write_available", - pa_get_stream_write_available, METH_VARARGS, - "get buffer available for writing"}, - - {"get_stream_read_available", - pa_get_stream_read_available, METH_VARARGS, - "get buffer available for reading"}, - - {NULL, NULL, 0, NULL} -}; - + /* version */ + {"get_version", pa_get_version, METH_VARARGS, "get version"}, + {"get_version_text", pa_get_version_text, METH_VARARGS, "get version text"}, + + /* inits */ + {"initialize", pa_initialize, METH_VARARGS, "initialize portaudio"}, + {"terminate", pa_terminate, METH_VARARGS, "terminate portaudio"}, + + /* host api */ + {"get_host_api_count", pa_get_host_api_count, METH_VARARGS, + "get host API count"}, + + {"get_default_host_api", pa_get_default_host_api, METH_VARARGS, + "get default host API index"}, + + {"host_api_type_id_to_host_api_index", + pa_host_api_type_id_to_host_api_index, METH_VARARGS, + "get default host API index"}, + + {"host_api_device_index_to_device_index", + pa_host_api_device_index_to_device_index, METH_VARARGS, + "get default host API index"}, + + {"get_host_api_info", pa_get_host_api_info, METH_VARARGS, + "get host api information"}, + + /* device api */ + {"get_device_count", pa_get_device_count, METH_VARARGS, + "get host API count"}, + + {"get_default_input_device", pa_get_default_input_device, METH_VARARGS, + "get default input device index"}, + + {"get_default_output_device", pa_get_default_output_device, METH_VARARGS, + "get default output device index"}, + + {"get_device_info", pa_get_device_info, METH_VARARGS, + "get device information"}, + + /* stream open/close */ + {"open", (PyCFunction)pa_open, METH_VARARGS | METH_KEYWORDS, + "open port audio stream"}, + {"close", pa_close, METH_VARARGS, "close port audio stream"}, + {"get_sample_size", pa_get_sample_size, METH_VARARGS, + "get sample size of a format in bytes"}, + {"is_format_supported", (PyCFunction)pa_is_format_supported, + METH_VARARGS | METH_KEYWORDS, + "returns whether specified format is supported"}, + + /* stream start/stop */ + {"start_stream", pa_start_stream, METH_VARARGS, "starts port audio stream"}, + {"stop_stream", pa_stop_stream, METH_VARARGS, "stops port audio stream"}, + {"abort_stream", pa_abort_stream, METH_VARARGS, "aborts port audio stream"}, + {"is_stream_stopped", pa_is_stream_stopped, METH_VARARGS, + "returns whether stream is stopped"}, + {"is_stream_active", pa_is_stream_active, METH_VARARGS, + "returns whether stream is active"}, + {"get_stream_time", pa_get_stream_time, METH_VARARGS, + "returns stream time"}, + {"get_stream_cpu_load", pa_get_stream_cpu_load, METH_VARARGS, + "returns stream CPU load -- always 0 for blocking mode"}, + + /* stream read/write */ + {"write_stream", pa_write_stream, METH_VARARGS, "write to stream"}, + {"read_stream", pa_read_stream, METH_VARARGS, "read from stream"}, + + {"get_stream_write_available", pa_get_stream_write_available, METH_VARARGS, + "get buffer available for writing"}, + + {"get_stream_read_available", pa_get_stream_read_available, METH_VARARGS, + "get buffer available for reading"}, + + {NULL, NULL, 0, NULL}}; /************************************************************ * @@ -157,510 +146,378 @@ static PyMethodDef paMethods[] = { * ************************************************************/ - /************************************************************* * PaDeviceInfo Type : Python object wrapper for PaDeviceInfo *************************************************************/ typedef struct { + // clang-format off PyObject_HEAD PaDeviceInfo *devInfo; + // clang-format on } _pyAudio_paDeviceInfo; - -/* sepcific getters into the PaDeviceInfo struct */ - -static PyObject * -_pyAudio_paDeviceInfo_get_structVersion(_pyAudio_paDeviceInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paDeviceInfo_get_structVersion( + _pyAudio_paDeviceInfo *self, void *closure) { if (!self->devInfo) { - PyErr_SetString(PyExc_AttributeError, - "No Device Info available"); + PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyLong_FromLong(self->devInfo->structVersion); } -static PyObject * -_pyAudio_paDeviceInfo_get_name(_pyAudio_paDeviceInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paDeviceInfo_get_name(_pyAudio_paDeviceInfo *self, + void *closure) { if ((!self->devInfo) || (self->devInfo->name == NULL)) { - PyErr_SetString(PyExc_AttributeError, - "No Device Info available"); + PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyBytes_FromString(self->devInfo->name); } -static PyObject * -_pyAudio_paDeviceInfo_get_hostApi(_pyAudio_paDeviceInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paDeviceInfo_get_hostApi(_pyAudio_paDeviceInfo *self, + void *closure) { if (!self->devInfo) { - PyErr_SetString(PyExc_AttributeError, - "No Device Info available"); + PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyLong_FromLong(self->devInfo->hostApi); } -static PyObject * -_pyAudio_paDeviceInfo_get_maxInputChannels(_pyAudio_paDeviceInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paDeviceInfo_get_maxInputChannels( + _pyAudio_paDeviceInfo *self, void *closure) { if (!self->devInfo) { - PyErr_SetString(PyExc_AttributeError, - "No Device Info available"); + PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyLong_FromLong(self->devInfo->maxInputChannels); } -static PyObject * -_pyAudio_paDeviceInfo_get_maxOutputChannels(_pyAudio_paDeviceInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paDeviceInfo_get_maxOutputChannels( + _pyAudio_paDeviceInfo *self, void *closure) { if (!self->devInfo) { - PyErr_SetString(PyExc_AttributeError, - "No Device Info available"); + PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyLong_FromLong(self->devInfo->maxOutputChannels); } -static PyObject * -_pyAudio_paDeviceInfo_get_defaultLowInputLatency(_pyAudio_paDeviceInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paDeviceInfo_get_defaultLowInputLatency( + _pyAudio_paDeviceInfo *self, void *closure) { if (!self->devInfo) { - PyErr_SetString(PyExc_AttributeError, - "No Device Info available"); + PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyFloat_FromDouble(self->devInfo->defaultLowInputLatency); } -static PyObject * -_pyAudio_paDeviceInfo_get_defaultLowOutputLatency(_pyAudio_paDeviceInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paDeviceInfo_get_defaultLowOutputLatency( + _pyAudio_paDeviceInfo *self, void *closure) { if (!self->devInfo) { - PyErr_SetString(PyExc_AttributeError, - "No Device Info available"); + PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyFloat_FromDouble(self->devInfo->defaultLowOutputLatency); } - -static PyObject * -_pyAudio_paDeviceInfo_get_defaultHighInputLatency(_pyAudio_paDeviceInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paDeviceInfo_get_defaultHighInputLatency( + _pyAudio_paDeviceInfo *self, void *closure) { if (!self->devInfo) { - PyErr_SetString(PyExc_AttributeError, - "No Device Info available"); + PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyFloat_FromDouble(self->devInfo->defaultHighInputLatency); } -static PyObject * -_pyAudio_paDeviceInfo_get_defaultHighOutputLatency(_pyAudio_paDeviceInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paDeviceInfo_get_defaultHighOutputLatency( + _pyAudio_paDeviceInfo *self, void *closure) { if (!self->devInfo) { - PyErr_SetString(PyExc_AttributeError, - "No Device Info available"); + PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyFloat_FromDouble(self->devInfo->defaultHighOutputLatency); } -static PyObject * -_pyAudio_paDeviceInfo_get_defaultSampleRate(_pyAudio_paDeviceInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paDeviceInfo_get_defaultSampleRate( + _pyAudio_paDeviceInfo *self, void *closure) { if (!self->devInfo) { - PyErr_SetString(PyExc_AttributeError, - "No Device Info available"); + PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyFloat_FromDouble(self->devInfo->defaultSampleRate); } - - -static int -_pyAudio_paDeviceInfo_antiset(_pyAudio_paDeviceInfo *self, - PyObject *value, - void *closure) -{ +static int _pyAudio_paDeviceInfo_antiset(_pyAudio_paDeviceInfo *self, + PyObject *value, void *closure) { /* read-only: do not allow users to change values */ PyErr_SetString(PyExc_AttributeError, - "Fields read-only: cannot modify values"); + "Fields read-only: cannot modify values"); return -1; } static PyGetSetDef _pyAudio_paDeviceInfo_getseters[] = { - {"name", - (getter) _pyAudio_paDeviceInfo_get_name, - (setter) _pyAudio_paDeviceInfo_antiset, - "device name", - NULL}, - - {"structVersion", - (getter) _pyAudio_paDeviceInfo_get_structVersion, - (setter) _pyAudio_paDeviceInfo_antiset, - "struct version", - NULL}, - - {"hostApi", - (getter) _pyAudio_paDeviceInfo_get_hostApi, - (setter) _pyAudio_paDeviceInfo_antiset, - "host api index", - NULL}, - - {"maxInputChannels", - (getter) _pyAudio_paDeviceInfo_get_maxInputChannels, - (setter) _pyAudio_paDeviceInfo_antiset, - "max input channels", - NULL}, - - {"maxOutputChannels", - (getter) _pyAudio_paDeviceInfo_get_maxOutputChannels, - (setter) _pyAudio_paDeviceInfo_antiset, - "max output channels", - NULL}, - - {"defaultLowInputLatency", - (getter) _pyAudio_paDeviceInfo_get_defaultLowInputLatency, - (setter) _pyAudio_paDeviceInfo_antiset, - "default low input latency", - NULL}, - - {"defaultLowOutputLatency", - (getter) _pyAudio_paDeviceInfo_get_defaultLowOutputLatency, - (setter) _pyAudio_paDeviceInfo_antiset, - "default low output latency", - NULL}, - - {"defaultHighInputLatency", - (getter) _pyAudio_paDeviceInfo_get_defaultHighInputLatency, - (setter) _pyAudio_paDeviceInfo_antiset, - "default high input latency", - NULL}, - - {"defaultHighOutputLatency", - (getter) _pyAudio_paDeviceInfo_get_defaultHighOutputLatency, - (setter) _pyAudio_paDeviceInfo_antiset, - "default high output latency", - NULL}, - - {"defaultSampleRate", - (getter) _pyAudio_paDeviceInfo_get_defaultSampleRate, - (setter) _pyAudio_paDeviceInfo_antiset, - "default sample rate", - NULL}, - - {NULL} -}; + {"name", (getter)_pyAudio_paDeviceInfo_get_name, + (setter)_pyAudio_paDeviceInfo_antiset, "device name", NULL}, -static void -_pyAudio_paDeviceInfo_dealloc(_pyAudio_paDeviceInfo* self) -{ - /* reset the pointer */ - self->devInfo = NULL; + {"structVersion", (getter)_pyAudio_paDeviceInfo_get_structVersion, + (setter)_pyAudio_paDeviceInfo_antiset, "struct version", NULL}, + + {"hostApi", (getter)_pyAudio_paDeviceInfo_get_hostApi, + (setter)_pyAudio_paDeviceInfo_antiset, "host api index", NULL}, + + {"maxInputChannels", (getter)_pyAudio_paDeviceInfo_get_maxInputChannels, + (setter)_pyAudio_paDeviceInfo_antiset, "max input channels", NULL}, + + {"maxOutputChannels", (getter)_pyAudio_paDeviceInfo_get_maxOutputChannels, + (setter)_pyAudio_paDeviceInfo_antiset, "max output channels", NULL}, - /* free the object */ - Py_TYPE(self)->tp_free((PyObject*) self); + {"defaultLowInputLatency", + (getter)_pyAudio_paDeviceInfo_get_defaultLowInputLatency, + (setter)_pyAudio_paDeviceInfo_antiset, "default low input latency", NULL}, + + {"defaultLowOutputLatency", + (getter)_pyAudio_paDeviceInfo_get_defaultLowOutputLatency, + (setter)_pyAudio_paDeviceInfo_antiset, "default low output latency", NULL}, + + {"defaultHighInputLatency", + (getter)_pyAudio_paDeviceInfo_get_defaultHighInputLatency, + (setter)_pyAudio_paDeviceInfo_antiset, "default high input latency", NULL}, + + {"defaultHighOutputLatency", + (getter)_pyAudio_paDeviceInfo_get_defaultHighOutputLatency, + (setter)_pyAudio_paDeviceInfo_antiset, "default high output latency", + NULL}, + + {"defaultSampleRate", (getter)_pyAudio_paDeviceInfo_get_defaultSampleRate, + (setter)_pyAudio_paDeviceInfo_antiset, "default sample rate", NULL}, + + {NULL}}; + +static void _pyAudio_paDeviceInfo_dealloc(_pyAudio_paDeviceInfo *self) { + self->devInfo = NULL; + Py_TYPE(self)->tp_free((PyObject *)self); } static PyTypeObject _pyAudio_paDeviceInfoType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_portaudio.paDeviceInfo", /*tp_name*/ - sizeof(_pyAudio_paDeviceInfo), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - (destructor) _pyAudio_paDeviceInfo_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - "Port Audio Device Info", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - 0, /* tp_members */ - _pyAudio_paDeviceInfo_getseters, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ + // clang-format off + PyVarObject_HEAD_INIT(NULL, 0) + // clang-format on + "_portaudio.paDeviceInfo", /*tp_name*/ + sizeof(_pyAudio_paDeviceInfo), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)_pyAudio_paDeviceInfo_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "Port Audio Device Info", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + _pyAudio_paDeviceInfo_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ }; -static _pyAudio_paDeviceInfo * -_create_paDeviceInfo_object(void) -{ +static _pyAudio_paDeviceInfo *_create_paDeviceInfo_object(void) { _pyAudio_paDeviceInfo *obj; - /* don't allow subclassing? */ - obj = (_pyAudio_paDeviceInfo *) PyObject_New(_pyAudio_paDeviceInfo, - &_pyAudio_paDeviceInfoType); - - /* obj = (_pyAudio_Stream*) - _pyAudio_StreamType.tp_alloc(&_pyAudio_StreamType, 0); */ + /* don't allow subclassing */ + obj = (_pyAudio_paDeviceInfo *)PyObject_New(_pyAudio_paDeviceInfo, + &_pyAudio_paDeviceInfoType); return obj; } - - - /************************************************************* * PaHostApi Info Python Object *************************************************************/ typedef struct { + // clang-format off PyObject_HEAD - PaHostApiInfo *apiInfo; + // clang-format on + PaHostApiInfo *apiInfo; } _pyAudio_paHostApiInfo; -/* sepcific getters into the PaDeviceInfo struct */ - -static PyObject * -_pyAudio_paHostApiInfo_get_structVersion(_pyAudio_paHostApiInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paHostApiInfo_get_structVersion( + _pyAudio_paHostApiInfo *self, void *closure) { if ((!self->apiInfo)) { - PyErr_SetString(PyExc_AttributeError, - "No HostApi Info available"); + PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } return PyLong_FromLong(self->apiInfo->structVersion); } -static PyObject * -_pyAudio_paHostApiInfo_get_type(_pyAudio_paHostApiInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paHostApiInfo_get_type(_pyAudio_paHostApiInfo *self, + void *closure) { if ((!self->apiInfo)) { - PyErr_SetString(PyExc_AttributeError, - "No HostApi Info available"); + PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } - return PyLong_FromLong((long) self->apiInfo->type); + return PyLong_FromLong((long)self->apiInfo->type); } -static PyObject * -_pyAudio_paHostApiInfo_get_name(_pyAudio_paHostApiInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paHostApiInfo_get_name(_pyAudio_paHostApiInfo *self, + void *closure) { if ((!self->apiInfo) || (self->apiInfo->name == NULL)) { - PyErr_SetString(PyExc_AttributeError, - "No HostApi Info available"); + PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } return PyUnicode_FromString(self->apiInfo->name); } -static PyObject * -_pyAudio_paHostApiInfo_get_deviceCount(_pyAudio_paHostApiInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paHostApiInfo_get_deviceCount( + _pyAudio_paHostApiInfo *self, void *closure) { if ((!self->apiInfo)) { - PyErr_SetString(PyExc_AttributeError, - "No HostApi Info available"); + PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } return PyLong_FromLong(self->apiInfo->deviceCount); } -static PyObject * -_pyAudio_paHostApiInfo_get_defaultInputDevice(_pyAudio_paHostApiInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paHostApiInfo_get_defaultInputDevice( + _pyAudio_paHostApiInfo *self, void *closure) { if ((!self->apiInfo)) { - PyErr_SetString(PyExc_AttributeError, - "No HostApi Info available"); + PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } return PyLong_FromLong(self->apiInfo->defaultInputDevice); } -static PyObject * -_pyAudio_paHostApiInfo_get_defaultOutputDevice(_pyAudio_paHostApiInfo *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_paHostApiInfo_get_defaultOutputDevice( + _pyAudio_paHostApiInfo *self, void *closure) { if ((!self->apiInfo)) { - PyErr_SetString(PyExc_AttributeError, - "No HostApi Info available"); + PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } return PyLong_FromLong(self->apiInfo->defaultOutputDevice); } -static int -_pyAudio_paHostApiInfo_antiset(_pyAudio_paDeviceInfo *self, - PyObject *value, - void *closure) -{ +static int _pyAudio_paHostApiInfo_antiset(_pyAudio_paDeviceInfo *self, + PyObject *value, void *closure) { /* read-only: do not allow users to change values */ PyErr_SetString(PyExc_AttributeError, - "Fields read-only: cannot modify values"); + "Fields read-only: cannot modify values"); return -1; } -static void -_pyAudio_paHostApiInfo_dealloc(_pyAudio_paHostApiInfo* self) -{ - /* reset the pointer */ +static void _pyAudio_paHostApiInfo_dealloc(_pyAudio_paHostApiInfo *self) { self->apiInfo = NULL; - - /* free the object */ - Py_TYPE(self)->tp_free((PyObject*) self); + Py_TYPE(self)->tp_free((PyObject *)self); } static PyGetSetDef _pyAudio_paHostApiInfo_getseters[] = { - {"name", - (getter) _pyAudio_paHostApiInfo_get_name, - (setter) _pyAudio_paHostApiInfo_antiset, - "host api name", - NULL}, - - {"structVersion", - (getter) _pyAudio_paHostApiInfo_get_structVersion, - (setter) _pyAudio_paHostApiInfo_antiset, - "struct version", - NULL}, - - {"type", - (getter) _pyAudio_paHostApiInfo_get_type, - (setter) _pyAudio_paHostApiInfo_antiset, - "host api type", - NULL}, - - {"deviceCount", - (getter) _pyAudio_paHostApiInfo_get_deviceCount, - (setter) _pyAudio_paHostApiInfo_antiset, - "number of devices", - NULL}, - - {"defaultInputDevice", - (getter) _pyAudio_paHostApiInfo_get_defaultInputDevice, - (setter) _pyAudio_paHostApiInfo_antiset, - "default input device index", - NULL}, - - {"defaultOutputDevice", - (getter) _pyAudio_paHostApiInfo_get_defaultOutputDevice, - (setter) _pyAudio_paDeviceInfo_antiset, - "default output device index", - NULL}, - - {NULL} -}; + {"name", (getter)_pyAudio_paHostApiInfo_get_name, + (setter)_pyAudio_paHostApiInfo_antiset, "host api name", NULL}, + + {"structVersion", (getter)_pyAudio_paHostApiInfo_get_structVersion, + (setter)_pyAudio_paHostApiInfo_antiset, "struct version", NULL}, + + {"type", (getter)_pyAudio_paHostApiInfo_get_type, + (setter)_pyAudio_paHostApiInfo_antiset, "host api type", NULL}, + + {"deviceCount", (getter)_pyAudio_paHostApiInfo_get_deviceCount, + (setter)_pyAudio_paHostApiInfo_antiset, "number of devices", NULL}, + + {"defaultInputDevice", + (getter)_pyAudio_paHostApiInfo_get_defaultInputDevice, + (setter)_pyAudio_paHostApiInfo_antiset, "default input device index", + NULL}, + + {"defaultOutputDevice", + (getter)_pyAudio_paHostApiInfo_get_defaultOutputDevice, + (setter)_pyAudio_paDeviceInfo_antiset, "default output device index", + NULL}, + + {NULL}}; static PyTypeObject _pyAudio_paHostApiInfoType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_portaudio.paHostApiInfo", /*tp_name*/ - sizeof(_pyAudio_paHostApiInfo), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - (destructor) _pyAudio_paHostApiInfo_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - "Port Audio HostApi Info", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - 0, /* tp_members */ - _pyAudio_paHostApiInfo_getseters, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ + // clang-format off + PyVarObject_HEAD_INIT(NULL, 0) + // clang-format on + "_portaudio.paHostApiInfo", /*tp_name*/ + sizeof(_pyAudio_paHostApiInfo), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)_pyAudio_paHostApiInfo_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "Port Audio HostApi Info", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + _pyAudio_paHostApiInfo_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ }; -static _pyAudio_paHostApiInfo * -_create_paHostApiInfo_object(void) -{ +static _pyAudio_paHostApiInfo *_create_paHostApiInfo_object(void) { _pyAudio_paHostApiInfo *obj; - /* don't allow subclassing? */ - obj = (_pyAudio_paHostApiInfo *) PyObject_New(_pyAudio_paHostApiInfo, - &_pyAudio_paHostApiInfoType); + /* don't allow subclassing */ + obj = (_pyAudio_paHostApiInfo *)PyObject_New(_pyAudio_paHostApiInfo, + &_pyAudio_paHostApiInfoType); return obj; } @@ -674,8 +531,10 @@ _create_paHostApiInfo_object(void) #ifdef MACOSX typedef struct { + // clang-format off PyObject_HEAD - PaMacCoreStreamInfo *paMacCoreStreamInfo; + // clang-format on + PaMacCoreStreamInfo *paMacCoreStreamInfo; int flags; SInt32 *channelMap; int channelMapSize; @@ -683,9 +542,8 @@ typedef struct { typedef _pyAudio_MacOSX_hostApiSpecificStreamInfo _pyAudio_Mac_HASSI; -static void -_pyAudio_MacOSX_hostApiSpecificStreamInfo_cleanup(_pyAudio_Mac_HASSI *self) -{ +static void _pyAudio_MacOSX_hostApiSpecificStreamInfo_cleanup( + _pyAudio_Mac_HASSI *self) { if (self->paMacCoreStreamInfo != NULL) { free(self->paMacCoreStreamInfo); self->paMacCoreStreamInfo = NULL; @@ -700,43 +558,37 @@ _pyAudio_MacOSX_hostApiSpecificStreamInfo_cleanup(_pyAudio_Mac_HASSI *self) self->channelMapSize = 0; } -static void -_pyAudio_MacOSX_hostApiSpecificStreamInfo_dealloc(_pyAudio_Mac_HASSI *self) -{ +static void _pyAudio_MacOSX_hostApiSpecificStreamInfo_dealloc( + _pyAudio_Mac_HASSI *self) { _pyAudio_MacOSX_hostApiSpecificStreamInfo_cleanup(self); - Py_TYPE(self)->tp_free((PyObject *) self); + Py_TYPE(self)->tp_free((PyObject *)self); } -static int -_pyAudio_MacOSX_hostApiSpecificStreamInfo_init(PyObject *_self, - PyObject *args, - PyObject *kwargs) -{ - _pyAudio_Mac_HASSI *self = (_pyAudio_Mac_HASSI *) _self; +static int _pyAudio_MacOSX_hostApiSpecificStreamInfo_init(PyObject *_self, + PyObject *args, + PyObject *kwargs) { + _pyAudio_Mac_HASSI *self = (_pyAudio_Mac_HASSI *)_self; PyObject *channel_map = NULL; int flags = paMacCorePlayNice; static char *kwlist[] = {"flags", "channel_map", NULL}; - if (! PyArg_ParseTupleAndKeywords(args, kwargs, "|iO", kwlist, - &flags, &channel_map)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iO", kwlist, &flags, + &channel_map)) { return -1; } - // cleanup (just in case) _pyAudio_MacOSX_hostApiSpecificStreamInfo_cleanup(self); if (channel_map != NULL) { - // ensure channel_map is an array - if (! PyTuple_Check(channel_map)) { + if (!PyTuple_Check(channel_map)) { PyErr_SetString(PyExc_ValueError, "Channel map must be a tuple"); return -1; } // generate SInt32 channelMap - self->channelMapSize = (int) PyTuple_Size(channel_map); - - self->channelMap = (SInt32 *) malloc(sizeof(SInt32) * self->channelMapSize); + self->channelMapSize = (int)PyTuple_Size(channel_map); + self->channelMap = (SInt32 *)malloc(sizeof(SInt32) * self->channelMapSize); if (self->channelMap == NULL) { PyErr_SetString(PyExc_SystemError, "Out of memory"); @@ -749,32 +601,27 @@ _pyAudio_MacOSX_hostApiSpecificStreamInfo_init(PyObject *_self, for (i = 0; i < self->channelMapSize; ++i) { element = PyTuple_GetItem(channel_map, i); if (element == NULL) { - // error condition - PyErr_SetString(PyExc_ValueError, - "Internal error: out of bounds index"); - _pyAudio_MacOSX_hostApiSpecificStreamInfo_cleanup(self); - return -1; + PyErr_SetString(PyExc_ValueError, + "Internal error: out of bounds index"); + _pyAudio_MacOSX_hostApiSpecificStreamInfo_cleanup(self); + return -1; } - // make sure element is an integer if (!PyNumber_Check(element)) { - PyErr_SetString(PyExc_ValueError, - "Channel Map must consist of integer elements"); - _pyAudio_MacOSX_hostApiSpecificStreamInfo_cleanup(self); - return -1; + PyErr_SetString(PyExc_ValueError, + "Channel Map must consist of integer elements"); + _pyAudio_MacOSX_hostApiSpecificStreamInfo_cleanup(self); + return -1; } PyObject *long_element = PyNumber_Long(element); - - // OK, looks good - self->channelMap[i] = (SInt32) PyLong_AsLong(long_element); + self->channelMap[i] = (SInt32)PyLong_AsLong(long_element); Py_DECREF(long_element); } } - // malloc self->paMacCoreStreamInfo self->paMacCoreStreamInfo = - (PaMacCoreStreamInfo *) malloc(sizeof(PaMacCoreStreamInfo)); + (PaMacCoreStreamInfo *)malloc(sizeof(PaMacCoreStreamInfo)); if (self->paMacCoreStreamInfo == NULL) { PyErr_SetString(PyExc_SystemError, "Out of memeory"); @@ -785,28 +632,21 @@ _pyAudio_MacOSX_hostApiSpecificStreamInfo_init(PyObject *_self, PaMacCore_SetupStreamInfo(self->paMacCoreStreamInfo, flags); if (self->channelMap) { - PaMacCore_SetupChannelMap(self->paMacCoreStreamInfo, - self->channelMap, - self->channelMapSize); + PaMacCore_SetupChannelMap(self->paMacCoreStreamInfo, self->channelMap, + self->channelMapSize); } self->flags = flags; - return 0; } -static PyObject * -_pyAudio_MacOSX_hostApiSpecificStreamInfo_get_flags(_pyAudio_Mac_HASSI *self, - void *closure) -{ +static PyObject *_pyAudio_MacOSX_hostApiSpecificStreamInfo_get_flags( + _pyAudio_Mac_HASSI *self, void *closure) { return PyLong_FromLong(self->flags); } -static PyObject * -_pyAudio_MacOSX_hostApiSpecificStreamInfo_get_channel_map( - _pyAudio_Mac_HASSI *self, - void *closure) -{ +static PyObject *_pyAudio_MacOSX_hostApiSpecificStreamInfo_get_channel_map( + _pyAudio_Mac_HASSI *self, void *closure) { if (self->channelMap == NULL || self->channelMapSize == 0) { Py_INCREF(Py_None); return Py_None; @@ -821,9 +661,8 @@ _pyAudio_MacOSX_hostApiSpecificStreamInfo_get_channel_map( return NULL; } - if (PyTuple_SetItem(channelMapTuple, - i, - PyLong_FromLong(self->channelMap[i]))) { + if (PyTuple_SetItem(channelMapTuple, i, + PyLong_FromLong(self->channelMap[i]))) { // non-zero on error PyErr_SetString(PyExc_SystemError, "Can't create channel map."); return NULL; @@ -832,77 +671,71 @@ _pyAudio_MacOSX_hostApiSpecificStreamInfo_get_channel_map( return channelMapTuple; } -static int -_pyAudio_MacOSX_hostApiSpecificStreamInfo_antiset(_pyAudio_Mac_HASSI *self, - PyObject *value, - void *closure) -{ +static int _pyAudio_MacOSX_hostApiSpecificStreamInfo_antiset( + _pyAudio_Mac_HASSI *self, PyObject *value, void *closure) { /* read-only: do not allow users to change values */ PyErr_SetString(PyExc_AttributeError, - "Fields read-only: cannot modify values"); + "Fields read-only: cannot modify values"); return -1; } static PyGetSetDef _pyAudio_MacOSX_hostApiSpecificStreamInfo_getseters[] = { - {"flags", - (getter) _pyAudio_MacOSX_hostApiSpecificStreamInfo_get_flags, - (setter) _pyAudio_MacOSX_hostApiSpecificStreamInfo_antiset, - "flags", - NULL}, - - {"channel_map", - (getter) _pyAudio_MacOSX_hostApiSpecificStreamInfo_get_channel_map, - (setter) _pyAudio_MacOSX_hostApiSpecificStreamInfo_antiset, - "channel map", - NULL}, - - {NULL} -}; + {"flags", (getter)_pyAudio_MacOSX_hostApiSpecificStreamInfo_get_flags, + (setter)_pyAudio_MacOSX_hostApiSpecificStreamInfo_antiset, "flags", NULL}, + + {"channel_map", + (getter)_pyAudio_MacOSX_hostApiSpecificStreamInfo_get_channel_map, + (setter)_pyAudio_MacOSX_hostApiSpecificStreamInfo_antiset, "channel map", + NULL}, + + {NULL}}; static PyTypeObject _pyAudio_MacOSX_hostApiSpecificStreamInfoType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_portaudio.PaMacCoreStreamInfo", /*tp_name*/ - sizeof(_pyAudio_MacOSX_hostApiSpecificStreamInfo), /*tp_basicsize*/ - 0, /*tp_itemsize*/ + // clang-format off + PyVarObject_HEAD_INIT(NULL, 0) + // clang-format on + "_portaudio.PaMacCoreStreamInfo", /*tp_name*/ + sizeof(_pyAudio_MacOSX_hostApiSpecificStreamInfo), /*tp_basicsize*/ + 0, /*tp_itemsize*/ /*tp_dealloc*/ - (destructor) _pyAudio_MacOSX_hostApiSpecificStreamInfo_dealloc, - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - "Mac OS X Specific HostAPI configuration", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - 0, /* tp_members */ - _pyAudio_MacOSX_hostApiSpecificStreamInfo_getseters, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (int (*)(PyObject*, PyObject*, PyObject*))_pyAudio_MacOSX_hostApiSpecificStreamInfo_init, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ + (destructor)_pyAudio_MacOSX_hostApiSpecificStreamInfo_dealloc, + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "Mac OS X Specific HostAPI configuration", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + _pyAudio_MacOSX_hostApiSpecificStreamInfo_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (int (*)(PyObject *, PyObject *, PyObject *)) + _pyAudio_MacOSX_hostApiSpecificStreamInfo_init, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ }; #endif - /************************************************************* * Stream Wrapper Python Object *************************************************************/ @@ -914,37 +747,30 @@ typedef struct { } PyAudioCallbackContext; typedef struct { + // clang-format off PyObject_HEAD + // clang-format on PaStream *stream; PaStreamParameters *inputParameters; PaStreamParameters *outputParameters; - - /* include PaStreamInfo too! */ PaStreamInfo *streamInfo; - - /* context for callback */ PyAudioCallbackContext *callbackContext; - int is_open; } _pyAudio_Stream; -static int -_is_open(_pyAudio_Stream *obj) { - return (obj) && (obj->is_open); -} +static int _is_open(_pyAudio_Stream *obj) { return (obj) && (obj->is_open); } -static void -_cleanup_Stream_object(_pyAudio_Stream *streamObject) -{ +static void _cleanup_Stream_object(_pyAudio_Stream *streamObject) { if (streamObject->stream != NULL) { + // clang-format off Py_BEGIN_ALLOW_THREADS Pa_CloseStream(streamObject->stream); Py_END_ALLOW_THREADS + // clang-format on streamObject->stream = NULL; } - if (streamObject->streamInfo) - streamObject->streamInfo = NULL; + if (streamObject->streamInfo) streamObject->streamInfo = NULL; if (streamObject->inputParameters != NULL) { free(streamObject->inputParameters); @@ -962,212 +788,156 @@ _cleanup_Stream_object(_pyAudio_Stream *streamObject) streamObject->callbackContext = NULL; } - /* designate the stream as closed */ streamObject->is_open = 0; } -static void -_pyAudio_Stream_dealloc(_pyAudio_Stream* self) -{ - /* deallocate memory if necessary */ +static void _pyAudio_Stream_dealloc(_pyAudio_Stream *self) { _cleanup_Stream_object(self); - - /* free the object */ - Py_TYPE(self)->tp_free((PyObject*) self); + Py_TYPE(self)->tp_free((PyObject *)self); } - -static PyObject * -_pyAudio_Stream_get_structVersion(_pyAudio_Stream *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_Stream_get_structVersion(_pyAudio_Stream *self, + void *closure) { if (!_is_open(self)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } if ((!self->streamInfo)) { - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "No StreamInfo available", - paBadStreamPtr)); + PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, + "No StreamInfo available")); return NULL; } return PyLong_FromLong(self->streamInfo->structVersion); } -static PyObject * -_pyAudio_Stream_get_inputLatency(_pyAudio_Stream *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_Stream_get_inputLatency(_pyAudio_Stream *self, + void *closure) { if (!_is_open(self)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - /* sanity check */ if ((!self->streamInfo)) { - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "No StreamInfo available", - paBadStreamPtr)); + PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, + "No StreamInfo available")); return NULL; } return PyFloat_FromDouble(self->streamInfo->inputLatency); } -static PyObject * -_pyAudio_Stream_get_outputLatency(_pyAudio_Stream *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_Stream_get_outputLatency(_pyAudio_Stream *self, + void *closure) { if (!_is_open(self)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - /* sanity check */ if ((!self->streamInfo)) { - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "No StreamInfo available", - paBadStreamPtr)); + PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, + "No StreamInfo available")); return NULL; } return PyFloat_FromDouble(self->streamInfo->outputLatency); } -static PyObject * -_pyAudio_Stream_get_sampleRate(_pyAudio_Stream *self, - void *closure) -{ - /* sanity check */ +static PyObject *_pyAudio_Stream_get_sampleRate(_pyAudio_Stream *self, + void *closure) { if (!_is_open(self)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - /* sanity check */ if ((!self->streamInfo)) { - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "No StreamInfo available", - paBadStreamPtr)); + PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, + "No StreamInfo available")); return NULL; } return PyFloat_FromDouble(self->streamInfo->sampleRate); } -static int -_pyAudio_Stream_antiset(_pyAudio_Stream *self, - PyObject *value, - void *closure) -{ +static int _pyAudio_Stream_antiset(_pyAudio_Stream *self, PyObject *value, + void *closure) { /* read-only: do not allow users to change values */ PyErr_SetString(PyExc_AttributeError, - "Fields read-only: cannot modify values"); + "Fields read-only: cannot modify values"); return -1; } static PyGetSetDef _pyAudio_Stream_getseters[] = { - {"structVersion", - (getter) _pyAudio_Stream_get_structVersion, - (setter) _pyAudio_Stream_antiset, - "struct version", - NULL}, - - {"inputLatency", - (getter) _pyAudio_Stream_get_inputLatency, - (setter) _pyAudio_Stream_antiset, - "input latency", - NULL}, - - {"outputLatency", - (getter) _pyAudio_Stream_get_outputLatency, - (setter) _pyAudio_Stream_antiset, - "output latency", - NULL}, - - {"sampleRate", - (getter) _pyAudio_Stream_get_sampleRate, - (setter) _pyAudio_Stream_antiset, - "sample rate", - NULL}, - - {NULL} -}; + {"structVersion", (getter)_pyAudio_Stream_get_structVersion, + (setter)_pyAudio_Stream_antiset, "struct version", NULL}, + + {"inputLatency", (getter)_pyAudio_Stream_get_inputLatency, + (setter)_pyAudio_Stream_antiset, "input latency", NULL}, + + {"outputLatency", (getter)_pyAudio_Stream_get_outputLatency, + (setter)_pyAudio_Stream_antiset, "output latency", NULL}, + + {"sampleRate", (getter)_pyAudio_Stream_get_sampleRate, + (setter)_pyAudio_Stream_antiset, "sample rate", NULL}, + + {NULL}}; static PyTypeObject _pyAudio_StreamType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_portaudio.Stream", /*tp_name*/ - sizeof(_pyAudio_Stream), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - (destructor) _pyAudio_Stream_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - "Port Audio Stream", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - 0, /* tp_members */ - _pyAudio_Stream_getseters, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ + // clang-format off + PyVarObject_HEAD_INIT(NULL, 0) + // clang-format on + "_portaudio.Stream", /*tp_name*/ + sizeof(_pyAudio_Stream), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)_pyAudio_Stream_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "Port Audio Stream", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + _pyAudio_Stream_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ }; -static _pyAudio_Stream * -_create_Stream_object(void) -{ +static _pyAudio_Stream *_create_Stream_object(void) { _pyAudio_Stream *obj; - /* don't allow subclassing? */ - obj = (_pyAudio_Stream *) PyObject_New(_pyAudio_Stream, - &_pyAudio_StreamType); + /* don't allow subclassing */ + obj = (_pyAudio_Stream *)PyObject_New(_pyAudio_Stream, &_pyAudio_StreamType); return obj; } - /************************************************************ * * III. PortAudio Method Implementations @@ -1178,20 +948,18 @@ _create_Stream_object(void) * Version Info *************************************************************/ -static PyObject * -pa_get_version(PyObject *self, PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) +static PyObject *pa_get_version(PyObject *self, PyObject *args) { + if (!PyArg_ParseTuple(args, "")) { return NULL; + } return PyLong_FromLong(Pa_GetVersion()); } -static PyObject * -pa_get_version_text(PyObject *self, PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) +static PyObject *pa_get_version_text(PyObject *self, PyObject *args) { + if (!PyArg_ParseTuple(args, "")) { return NULL; + } return PyUnicode_FromString(Pa_GetVersionText()); } @@ -1200,21 +968,30 @@ pa_get_version_text(PyObject *self, PyObject *args) * Initialization/Termination *************************************************************/ -static PyObject * -pa_initialize(PyObject *self, PyObject *args) -{ +static PyObject *pa_initialize(PyObject *self, PyObject *args) { int err; + + // clang-format off + Py_BEGIN_ALLOW_THREADS err = Pa_Initialize(); + Py_END_ALLOW_THREADS + // clang-format on + if (err != paNoError) { + // clang-format off + Py_BEGIN_ALLOW_THREADS Pa_Terminate(); + Py_END_ALLOW_THREADS + // clang-format on + #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif + PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(err), err)); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -1222,10 +999,13 @@ pa_initialize(PyObject *self, PyObject *args) return Py_None; } -static PyObject * -pa_terminate(PyObject *self, PyObject *args) -{ +static PyObject *pa_terminate(PyObject *self, PyObject *args) { + // clang-format off + Py_BEGIN_ALLOW_THREADS Pa_Terminate(); + Py_END_ALLOW_THREADS + // clang-format on + Py_INCREF(Py_None); return Py_None; } @@ -1234,18 +1014,16 @@ pa_terminate(PyObject *self, PyObject *args) * HostAPI *************************************************************/ -static PyObject * -pa_get_host_api_count(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_host_api_count(PyObject *self, PyObject *args) { PaHostApiIndex count; - if (!PyArg_ParseTuple(args, "")) + if (!PyArg_ParseTuple(args, "")) { return NULL; + } count = Pa_GetHostApiCount(); if (count < 0) { - #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", count); @@ -1253,26 +1031,23 @@ pa_get_host_api_count(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(count), count)); + Py_BuildValue("(i,s)", count, Pa_GetErrorText(count))); return NULL; } return PyLong_FromLong(count); } -static PyObject * -pa_get_default_host_api(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_default_host_api(PyObject *self, PyObject *args) { PaHostApiIndex index; - if (!PyArg_ParseTuple(args, "")) + if (!PyArg_ParseTuple(args, "")) { return NULL; + } index = Pa_GetDefaultHostApi(); if (index < 0) { - #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", index); @@ -1280,27 +1055,25 @@ pa_get_default_host_api(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(index), index)); + Py_BuildValue("(i,s)", index, Pa_GetErrorText(index))); return NULL; } return PyLong_FromLong(index); } -static PyObject * -pa_host_api_type_id_to_host_api_index(PyObject *self, PyObject *args) -{ +static PyObject *pa_host_api_type_id_to_host_api_index(PyObject *self, + PyObject *args) { PaHostApiTypeId typeid; PaHostApiIndex index; - if (!PyArg_ParseTuple(args, "i", &typeid)) + if (!PyArg_ParseTuple(args, "i", &typeid)) { return NULL; + } index = Pa_HostApiTypeIdToHostApiIndex(typeid); if (index < 0) { - #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", index); @@ -1308,84 +1081,73 @@ pa_host_api_type_id_to_host_api_index(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(index), index)); + Py_BuildValue("(i,s)", index, Pa_GetErrorText(index))); return NULL; } return PyLong_FromLong(index); } -static PyObject * -pa_host_api_device_index_to_device_index(PyObject *self, PyObject *args) -{ +static PyObject *pa_host_api_device_index_to_device_index(PyObject *self, + PyObject *args) { PaHostApiIndex apiIndex; int hostApiDeviceindex; PaDeviceIndex devIndex; - - if (!PyArg_ParseTuple(args, "ii", &apiIndex, &hostApiDeviceindex)) + if (!PyArg_ParseTuple(args, "ii", &apiIndex, &hostApiDeviceindex)) { return NULL; + } devIndex = Pa_HostApiDeviceIndexToDeviceIndex(apiIndex, hostApiDeviceindex); if (devIndex < 0) { - #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", devIndex); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(devIndex)); #endif - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(devIndex), devIndex)); + PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", devIndex, + Pa_GetErrorText(devIndex))); return NULL; } return PyLong_FromLong(devIndex); } -static PyObject * -pa_get_host_api_info(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_host_api_info(PyObject *self, PyObject *args) { PaHostApiIndex index; - PaHostApiInfo* _info; - _pyAudio_paHostApiInfo* py_info; + PaHostApiInfo *_info; + _pyAudio_paHostApiInfo *py_info; - if (!PyArg_ParseTuple(args, "i", &index)) + if (!PyArg_ParseTuple(args, "i", &index)) { return NULL; + } - _info = (PaHostApiInfo *) Pa_GetHostApiInfo(index); - + _info = (PaHostApiInfo *)Pa_GetHostApiInfo(index); if (!_info) { - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Invalid host api info", - paInvalidHostApi)); + PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInvalidHostApi, + "Invalid host api info")); return NULL; } py_info = _create_paHostApiInfo_object(); py_info->apiInfo = _info; - - return (PyObject *) py_info; + return (PyObject *)py_info; } /************************************************************* * Device API *************************************************************/ -static PyObject * -pa_get_device_count(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_device_count(PyObject *self, PyObject *args) { PaDeviceIndex count; - if (!PyArg_ParseTuple(args, "")) + if (!PyArg_ParseTuple(args, "")) { return NULL; + } count = Pa_GetDeviceCount(); if (count < 0) { - #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", count); @@ -1393,28 +1155,25 @@ pa_get_device_count(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(count), count)); + Py_BuildValue("(i,s)", count, Pa_GetErrorText(count))); return NULL; } return PyLong_FromLong(count); } -static PyObject * -pa_get_default_input_device(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_default_input_device(PyObject *self, PyObject *args) { PaDeviceIndex index; - if (!PyArg_ParseTuple(args, "")) + if (!PyArg_ParseTuple(args, "")) { return NULL; + } index = Pa_GetDefaultInputDevice(); if (index == paNoDevice) { PyErr_SetString(PyExc_IOError, "No Default Input Device Available"); return NULL; } else if (index < 0) { - #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", index); @@ -1422,28 +1181,25 @@ pa_get_default_input_device(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(index), index)); + Py_BuildValue("(i,s)", index, Pa_GetErrorText(index))); return NULL; } return PyLong_FromLong(index); } -static PyObject * -pa_get_default_output_device(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_default_output_device(PyObject *self, PyObject *args) { PaDeviceIndex index; - if (!PyArg_ParseTuple(args, "")) + if (!PyArg_ParseTuple(args, "")) { return NULL; + } index = Pa_GetDefaultOutputDevice(); if (index == paNoDevice) { PyErr_SetString(PyExc_IOError, "No Default Output Device Available"); return NULL; } else if (index < 0) { - #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", index); @@ -1451,50 +1207,43 @@ pa_get_default_output_device(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(index), index)); + Py_BuildValue("(i,s)", index, Pa_GetErrorText(index))); return NULL; } return PyLong_FromLong(index); } -static PyObject * -pa_get_device_info(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_device_info(PyObject *self, PyObject *args) { PaDeviceIndex index; - PaDeviceInfo* _info; - _pyAudio_paDeviceInfo* py_info; + PaDeviceInfo *_info; + _pyAudio_paDeviceInfo *py_info; - if (!PyArg_ParseTuple(args, "i", &index)) + if (!PyArg_ParseTuple(args, "i", &index)) { return NULL; + } - _info = (PaDeviceInfo *) Pa_GetDeviceInfo(index); - + _info = (PaDeviceInfo *)Pa_GetDeviceInfo(index); if (!_info) { - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Invalid device info", paInvalidDevice)); + PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInvalidDevice, + "Invalid device info")); return NULL; } py_info = _create_paDeviceInfo_object(); py_info->devInfo = _info; - return (PyObject *) py_info; + return (PyObject *)py_info; } /************************************************************* * Stream Open / Close / Supported *************************************************************/ -int -_stream_callback_cfunction(const void *input, - void *output, - unsigned long frameCount, - const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData) -{ +int _stream_callback_cfunction(const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo *timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData) { int return_val = paAbort; PyGILState_STATE _state = PyGILState_Ensure(); @@ -1525,6 +1274,7 @@ _stream_callback_cfunction(const void *input, long main_thread_id = context->main_thread_id; PyObject *py_frame_count = PyLong_FromUnsignedLong(frameCount); + // clang-format off PyObject *py_time_info = Py_BuildValue("{s:d,s:d,s:d}", "input_buffer_adc_time", timeInfo->inputBufferAdcTime, @@ -1532,23 +1282,21 @@ _stream_callback_cfunction(const void *input, timeInfo->currentTime, "output_buffer_dac_time", timeInfo->outputBufferDacTime); + // clang-format on PyObject *py_status_flags = PyLong_FromUnsignedLong(statusFlags); PyObject *py_input_data = Py_None; const char *pData; - int output_len; + unsigned output_len; PyObject *py_result; if (input) { - py_input_data = PyBytes_FromStringAndSize(input, - bytes_per_frame * frameCount); + py_input_data = + PyBytes_FromStringAndSize(input, bytes_per_frame * frameCount); } - py_result = PyObject_CallFunctionObjArgs(py_callback, - py_input_data, - py_frame_count, - py_time_info, - py_status_flags, - NULL); + py_result = + PyObject_CallFunctionObjArgs(py_callback, py_input_data, py_frame_count, + py_time_info, py_status_flags, NULL); if (py_result == NULL) { #ifdef VERBOSE @@ -1558,23 +1306,23 @@ _stream_callback_cfunction(const void *input, PyObject *err = PyErr_Occurred(); if (err) { - PyThreadState_SetAsyncExc(main_thread_id, err); - - // Print out a stack trace to help debugging. - // TODO: make VERBOSE a runtime flag so users can control - // the amount of logging. - PyErr_Print(); + PyThreadState_SetAsyncExc(main_thread_id, err); + // Print out a stack trace to help debugging. + // TODO: make VERBOSE a runtime flag so users can control + // the amount of logging. + PyErr_Print(); } goto end; } - + // clang-format off if (!PyArg_ParseTuple(py_result, "z#i", &pData, &output_len, &return_val)) { +// clang-format on #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error message: Could not parse callback return value\n"); @@ -1583,12 +1331,11 @@ _stream_callback_cfunction(const void *input, PyObject *err = PyErr_Occurred(); if (err) { - PyThreadState_SetAsyncExc(main_thread_id, err); - - // Print out a stack trace to help debugging. - // TODO: make VERBOSE a runtime flag so users can control - // the amount of logging. - PyErr_Print(); + PyThreadState_SetAsyncExc(main_thread_id, err); + // Print out a stack trace to help debugging. + // TODO: make VERBOSE a runtime flag so users can control + // the amount of logging. + PyErr_Print(); } Py_XDECREF(py_result); @@ -1596,40 +1343,34 @@ _stream_callback_cfunction(const void *input, goto end; } - Py_DECREF(py_result); - - if ((return_val != paComplete) && - (return_val != paAbort) && + if ((return_val != paComplete) && (return_val != paAbort) && (return_val != paContinue)) { - PyErr_SetString(PyExc_ValueError, - "Invalid PaStreamCallbackResult from callback"); - PyThreadState_SetAsyncExc(main_thread_id, PyErr_Occurred()); - PyErr_Print(); - - // Quit the callback loop - return_val = paAbort; + PyErr_SetString(PyExc_ValueError, + "Invalid PaStreamCallbackResult from callback"); + PyThreadState_SetAsyncExc(main_thread_id, PyErr_Occurred()); + PyErr_Print(); - goto end; + // Quit the callback loop + Py_DECREF(py_result); + return_val = paAbort; + goto end; } // Copy bytes for playback only if this is an output stream: - if (output) { - char *output_data = (char*)output; - memcpy(output_data, pData, min(output_len, bytes_per_frame * frameCount)); - - // Pad out the rest of the buffer with 0s if callback returned - // too few frames (and assume paComplete). - if (output_len < (frameCount * bytes_per_frame)) { - memset(output_data + output_len, - 0, - (frameCount * bytes_per_frame) - output_len); - return_val = paComplete; - } + char *output_data = (char *)output; + memcpy(output_data, pData, ADDRESS_MIN(output_len, bytes_per_frame * frameCount)); + // Pad out the rest of the buffer with 0s if callback returned + // too few frames (and assume paComplete). + if (output_len < (frameCount * bytes_per_frame)) { + memset(output_data + output_len, 0, + (frameCount * bytes_per_frame) - output_len); + return_val = paComplete; + } } + Py_DECREF(py_result); - end: - +end: if (input) { // Decrement this at the end, after memcpy, in case the user // returns py_input_data back for playback. @@ -1644,9 +1385,7 @@ _stream_callback_cfunction(const void *input, return return_val; } -static PyObject * -pa_open(PyObject *self, PyObject *args, PyObject *kwargs) -{ +static PyObject *pa_open(PyObject *self, PyObject *args, PyObject *kwargs) { int rate, channels; int input, output, frames_per_buffer; int input_device_index = -1; @@ -1665,23 +1404,21 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) PyAudioCallbackContext *context = NULL; _pyAudio_Stream *streamObject; - /* pass in rate, channel, width */ static char *kwlist[] = {"rate", - "channels", - "format", - "input", - "output", - "input_device_index", - "output_device_index", - "frames_per_buffer", - "input_host_api_specific_stream_info", - "output_host_api_specific_stream_info", - "stream_callback", - NULL}; + "channels", + "format", + "input", + "output", + "input_device_index", + "output_device_index", + "frames_per_buffer", + "input_host_api_specific_stream_info", + "output_host_api_specific_stream_info", + "stream_callback", + NULL}; #ifdef MACOSX - _pyAudio_MacOSX_hostApiSpecificStreamInfo *inputHostSpecificStreamInfo = - NULL; + _pyAudio_MacOSX_hostApiSpecificStreamInfo *inputHostSpecificStreamInfo = NULL; _pyAudio_MacOSX_hostApiSpecificStreamInfo *outputHostSpecificStreamInfo = NULL; #else @@ -1695,57 +1432,54 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) output = 0; frames_per_buffer = DEFAULT_FRAMES_PER_BUFFER; + // clang-format off if (!PyArg_ParseTupleAndKeywords(args, kwargs, #ifdef MACOSX - "iik|iiOOiO!O!O", + "iik|iiOOiO!O!O", #else - "iik|iiOOiOOO", + "iik|iiOOiOOO", #endif - kwlist, - &rate, &channels, &format, - &input, &output, - &input_device_index_arg, - &output_device_index_arg, - &frames_per_buffer, + kwlist, + &rate, &channels, &format, + &input, &output, + &input_device_index_arg, + &output_device_index_arg, + &frames_per_buffer, #ifdef MACOSX - &_pyAudio_MacOSX_hostApiSpecificStreamInfoType, + &_pyAudio_MacOSX_hostApiSpecificStreamInfoType, #endif - &inputHostSpecificStreamInfo, + &inputHostSpecificStreamInfo, #ifdef MACOSX - &_pyAudio_MacOSX_hostApiSpecificStreamInfoType, + &_pyAudio_MacOSX_hostApiSpecificStreamInfoType, #endif - &outputHostSpecificStreamInfo, - &stream_callback)) + &outputHostSpecificStreamInfo, + &stream_callback)) { return NULL; + } + // clang-format on if (stream_callback && (PyCallable_Check(stream_callback) == 0)) { - PyErr_SetString(PyExc_TypeError, "stream_callback must be callable"); - return NULL; + PyErr_SetString(PyExc_TypeError, "stream_callback must be callable"); + return NULL; } - /* check to see if device indices were specified */ - if ((input_device_index_arg == NULL) || - (input_device_index_arg == Py_None)) { - + if ((input_device_index_arg == NULL) || (input_device_index_arg == Py_None)) { #ifdef VERBOSE printf("Using default input device\n"); #endif input_device_index = -1; - } else { - // Support both Python 2 and Python 3 by using PyNumber_Check if (!PyNumber_Check(input_device_index_arg)) { PyErr_SetString(PyExc_ValueError, - "input_device_index must be integer (or None)"); + "input_device_index must be integer (or None)"); return NULL; } - input_device_index_long = - PyNumber_Long(input_device_index_arg); + input_device_index_long = PyNumber_Long(input_device_index_arg); - input_device_index = (int) PyLong_AsLong(input_device_index_long); + input_device_index = (int)PyLong_AsLong(input_device_index_long); Py_DECREF(input_device_index_long); #ifdef VERBOSE @@ -1755,24 +1489,20 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) if ((output_device_index_arg == NULL) || (output_device_index_arg == Py_None)) { - #ifdef VERBOSE printf("Using default output device\n"); #endif output_device_index = -1; - } else { - // Support both Python 2 and Python 3 by using PyNumber_Check if (!PyNumber_Check(output_device_index_arg)) { PyErr_SetString(PyExc_ValueError, - "output_device_index must be integer (or None)"); + "output_device_index must be integer (or None)"); return NULL; } - output_device_index_long = - PyNumber_Long(output_device_index_arg); - output_device_index = (int) PyLong_AsLong(output_device_index_long); + output_device_index_long = PyNumber_Long(output_device_index_arg); + output_device_index = (int)PyLong_AsLong(output_device_index_long); Py_DECREF(output_device_index_long); #ifdef VERBOSE @@ -1780,7 +1510,6 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) #endif } - /* sanity checks */ if (input == 0 && output == 0) { PyErr_SetString(PyExc_ValueError, "Must specify either input or output"); return NULL; @@ -1792,49 +1521,43 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) } if (output) { - outputParameters = - (PaStreamParameters *) malloc(sizeof(PaStreamParameters)); + outputParameters = (PaStreamParameters *)malloc(sizeof(PaStreamParameters)); - - if (output_device_index < 0) - /* default output device */ + if (output_device_index < 0) { outputParameters->device = Pa_GetDefaultOutputDevice(); - else + } else { outputParameters->device = output_device_index; + } /* final check -- ensure that there is a default device */ if (outputParameters->device < 0 || outputParameters->device >= Pa_GetDeviceCount()) { free(outputParameters); PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Invalid output device " - "(no default output device)", - paInvalidDevice)); + Py_BuildValue("(i,s)", paInvalidDevice, + "Invalid output device " + "(no default output device)")); return NULL; } outputParameters->channelCount = channels; outputParameters->sampleFormat = format; outputParameters->suggestedLatency = - Pa_GetDeviceInfo(outputParameters->device)->defaultLowOutputLatency; + Pa_GetDeviceInfo(outputParameters->device)->defaultLowOutputLatency; outputParameters->hostApiSpecificStreamInfo = NULL; #ifdef MACOSX if (outputHostSpecificStreamInfo) { outputParameters->hostApiSpecificStreamInfo = - outputHostSpecificStreamInfo->paMacCoreStreamInfo; + outputHostSpecificStreamInfo->paMacCoreStreamInfo; } #endif - } if (input) { - inputParameters = - (PaStreamParameters *) malloc(sizeof(PaStreamParameters)); + inputParameters = (PaStreamParameters *)malloc(sizeof(PaStreamParameters)); if (input_device_index < 0) { - /* default output device */ inputParameters->device = Pa_GetDefaultInputDevice(); } else { inputParameters->device = input_device_index; @@ -1844,56 +1567,55 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) if (inputParameters->device < 0) { free(inputParameters); PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Invalid input device " - "(no default output device)", - paInvalidDevice)); + Py_BuildValue("(i,s)", paInvalidDevice, + "Invalid input device " + "(no default output device)")); return NULL; } inputParameters->channelCount = channels; inputParameters->sampleFormat = format; inputParameters->suggestedLatency = - Pa_GetDeviceInfo(inputParameters->device)->defaultLowInputLatency; + Pa_GetDeviceInfo(inputParameters->device)->defaultLowInputLatency; inputParameters->hostApiSpecificStreamInfo = NULL; #ifdef MACOSX if (inputHostSpecificStreamInfo) { inputParameters->hostApiSpecificStreamInfo = - inputHostSpecificStreamInfo->paMacCoreStreamInfo; + inputHostSpecificStreamInfo->paMacCoreStreamInfo; } #endif - } - // Handle callback mode: if (stream_callback) { Py_INCREF(stream_callback); - context = (PyAudioCallbackContext *) malloc(sizeof(PyAudioCallbackContext)); - context->callback = (PyObject *) stream_callback; + context = (PyAudioCallbackContext *)malloc(sizeof(PyAudioCallbackContext)); + context->callback = (PyObject *)stream_callback; context->main_thread_id = PyThreadState_Get()->thread_id; context->frame_size = Pa_GetSampleSize(format) * channels; } + // clang-format off + Py_BEGIN_ALLOW_THREADS err = Pa_OpenStream(&stream, - /* input/output parameters */ - /* NULL values are ignored */ - inputParameters, - outputParameters, - /* Samples Per Second */ - rate, - /* allocate frames in the buffer */ - frames_per_buffer, - /* we won't output out of range samples - so don't bother clipping them */ - paClipOff, - /* callback, if specified */ - (stream_callback)?(_stream_callback_cfunction):(NULL), - /* callback userData, if applicable */ - context); + /* input/output parameters */ + /* NULL values are ignored */ + inputParameters, outputParameters, + /* samples per second */ + rate, + /* frames in the buffer */ + frames_per_buffer, + /* we won't output out of range samples + so don't bother clipping them */ + paClipOff, + /* callback, if specified */ + (stream_callback) ? (_stream_callback_cfunction) : (NULL), + /* callback userData, if applicable */ + context); + Py_END_ALLOW_THREADS + // clang-format on if (err != paNoError) { - #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); @@ -1901,18 +1623,16 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(err), err)); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } - streamInfo = (PaStreamInfo *) Pa_GetStreamInfo(stream); + streamInfo = (PaStreamInfo *)Pa_GetStreamInfo(stream); if (!streamInfo) { - /* Pa_Terminate(); */ PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Could not get stream information", - paInternalError)); + Py_BuildValue("(i,s)", paInternalError, + "Could not get stream information")); + return NULL; } @@ -1923,20 +1643,18 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) streamObject->is_open = 1; streamObject->streamInfo = streamInfo; streamObject->callbackContext = context; - - return (PyObject *) streamObject; + return (PyObject *)streamObject; } -static PyObject * -pa_close(PyObject *self, PyObject *args) -{ +static PyObject *pa_close(PyObject *self, PyObject *args) { PyObject *stream_arg; _pyAudio_Stream *streamObject; - if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) + if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) { return NULL; + } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; _cleanup_Stream_object(streamObject); @@ -1944,44 +1662,40 @@ pa_close(PyObject *self, PyObject *args) return Py_None; } -static PyObject * -pa_get_sample_size(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_sample_size(PyObject *self, PyObject *args) { PaSampleFormat format; int size_in_bytes; - if (!PyArg_ParseTuple(args, "k", &format)) + if (!PyArg_ParseTuple(args, "k", &format)) { return NULL; + } size_in_bytes = Pa_GetSampleSize(format); if (size_in_bytes < 0) { - PyErr_SetObject(PyExc_ValueError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(size_in_bytes), - size_in_bytes)); + PyErr_SetObject( + PyExc_ValueError, + Py_BuildValue("(s,i)", Pa_GetErrorText(size_in_bytes), size_in_bytes)); return NULL; } return PyLong_FromLong(size_in_bytes); } - -static PyObject * -pa_is_format_supported(PyObject *self, PyObject *args, - PyObject *kwargs) -{ - /* pass in rate, channel, width */ +static PyObject *pa_is_format_supported(PyObject *self, PyObject *args, + PyObject *kwargs) { + // clang-format off static char *kwlist[] = { - "sample_rate", - "input_device", - "input_channels", - "input_format", - "output_device", - "output_channels", - "output_format", - NULL + "sample_rate", + "input_device", + "input_channels", + "input_format", + "output_device", + "output_channels", + "output_format", + NULL }; + // clang-format on int input_device, input_channels; int output_device, output_channels; @@ -1991,20 +1705,22 @@ pa_is_format_supported(PyObject *self, PyObject *args, PaSampleFormat input_format, output_format; PaError error; - input_device = input_channels = - output_device = output_channels = -1; + input_device = input_channels = output_device = output_channels = -1; input_format = output_format = -1; + // clang-format off if (!PyArg_ParseTupleAndKeywords(args, kwargs, "f|iikiik", kwlist, - &sample_rate, - &input_device, - &input_channels, - &input_format, - &output_device, - &output_channels, - &output_format)) + &sample_rate, + &input_device, + &input_channels, + &input_format, + &output_device, + &output_channels, + &output_format)) { return NULL; + } + // clang-format on if (!(input_device < 0)) { inputParams.device = input_device; @@ -2023,17 +1739,15 @@ pa_is_format_supported(PyObject *self, PyObject *args, } error = Pa_IsFormatSupported((input_device < 0) ? NULL : &inputParams, - (output_device < 0) ? NULL : &outputParams, - sample_rate); + (output_device < 0) ? NULL : &outputParams, + sample_rate); if (error == paFormatIsSupported) { Py_INCREF(Py_True); return Py_True; } else { PyErr_SetObject(PyExc_ValueError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(error), - error)); + Py_BuildValue("(s,i)", Pa_GetErrorText(error), error)); return NULL; } } @@ -2042,31 +1756,31 @@ pa_is_format_supported(PyObject *self, PyObject *args, * Stream Start / Stop / Info *************************************************************/ -static PyObject * -pa_start_stream(PyObject *self, PyObject *args) -{ +static PyObject *pa_start_stream(PyObject *self, PyObject *args) { int err; PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; - if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) + if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) { return NULL; + } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - stream = streamObject->stream; + // clang-format off + Py_BEGIN_ALLOW_THREADS + err = Pa_StartStream(streamObject->stream); + Py_END_ALLOW_THREADS + // clang-format on - if ( ((err = Pa_StartStream(stream)) != paNoError) && - (err != paStreamIsNotStopped)) { + if ((err != paNoError) && + (err != paStreamIsNotStopped)) { _cleanup_Stream_object(streamObject); #ifdef VERBOSE @@ -2076,9 +1790,7 @@ pa_start_stream(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(err), - err)); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -2086,30 +1798,27 @@ pa_start_stream(PyObject *self, PyObject *args) return Py_None; } -static PyObject * -pa_stop_stream(PyObject *self, PyObject *args) -{ - +static PyObject *pa_stop_stream(PyObject *self, PyObject *args) { int err; PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; - if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) + if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) { return NULL; + } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; if (!_is_open(streamObject)) { PyErr_SetString(PyExc_IOError, "Stream not open"); return NULL; } - stream = streamObject->stream; - + // clang-format off Py_BEGIN_ALLOW_THREADS - err = Pa_StopStream(stream); + err = Pa_StopStream(streamObject->stream); Py_END_ALLOW_THREADS + // clang-format on if ((err != paNoError) && (err != paStreamIsStopped)) { _cleanup_Stream_object(streamObject); @@ -2121,9 +1830,7 @@ pa_stop_stream(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(err), - err)); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -2131,29 +1838,27 @@ pa_stop_stream(PyObject *self, PyObject *args) return Py_None; } -static PyObject * -pa_abort_stream(PyObject *self, PyObject *args) -{ +static PyObject *pa_abort_stream(PyObject *self, PyObject *args) { int err; PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; - if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) + if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) { return NULL; + } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; if (!_is_open(streamObject)) { PyErr_SetString(PyExc_IOError, "Stream not open"); return NULL; } - stream = streamObject->stream; - + // clang-format off Py_BEGIN_ALLOW_THREADS - err = Pa_AbortStream(stream); + err = Pa_AbortStream(streamObject->stream); Py_END_ALLOW_THREADS + // clang-format on if ((err != paNoError) && (err != paStreamIsStopped)) { _cleanup_Stream_object(streamObject); @@ -2165,9 +1870,7 @@ pa_abort_stream(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(err), - err)); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -2175,30 +1878,30 @@ pa_abort_stream(PyObject *self, PyObject *args) return Py_None; } -static PyObject * -pa_is_stream_stopped(PyObject *self, PyObject *args) -{ +static PyObject *pa_is_stream_stopped(PyObject *self, PyObject *args) { int err; PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; - if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) + if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) { return NULL; + } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - stream = streamObject->stream; + // clang-format off + Py_BEGIN_ALLOW_THREADS + err = Pa_IsStreamStopped(streamObject->stream); + Py_END_ALLOW_THREADS + // clang-format on - if ((err = Pa_IsStreamStopped(stream)) < 0) { + if (err < 0) { _cleanup_Stream_object(streamObject); #ifdef VERBOSE @@ -2208,9 +1911,7 @@ pa_is_stream_stopped(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(err), - err)); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -2223,28 +1924,29 @@ pa_is_stream_stopped(PyObject *self, PyObject *args) return Py_False; } -static PyObject * -pa_is_stream_active(PyObject *self, PyObject *args) -{ - +static PyObject *pa_is_stream_active(PyObject *self, PyObject *args) { int err; PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; - if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) + if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) { return NULL; + } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; if (!_is_open(streamObject)) { PyErr_SetString(PyExc_IOError, "Stream not open"); return NULL; } - stream = streamObject->stream; + // clang-format off + Py_BEGIN_ALLOW_THREADS + err = Pa_IsStreamActive(streamObject->stream); + Py_END_ALLOW_THREADS + // clang-format on - if ((err = Pa_IsStreamActive(stream)) < 0) { + if (err < 0) { _cleanup_Stream_object(streamObject); #ifdef VERBOSE @@ -2254,9 +1956,7 @@ pa_is_stream_active(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(err), - err)); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -2269,73 +1969,70 @@ pa_is_stream_active(PyObject *self, PyObject *args) return Py_False; } -static PyObject * -pa_get_stream_time(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_stream_time(PyObject *self, PyObject *args) { double time; PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; - if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) + if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) { return NULL; + } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - stream = streamObject->stream; + // clang-format off + Py_BEGIN_ALLOW_THREADS + time = Pa_GetStreamTime(streamObject->stream); + Py_END_ALLOW_THREADS + // clang-format on - if ((time = Pa_GetStreamTime(stream)) == 0) { + if (time == 0) { _cleanup_Stream_object(streamObject); PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Internal Error", - paInternalError)); + Py_BuildValue("(i,s)", paInternalError, "Internal Error")); return NULL; } return PyFloat_FromDouble(time); } -static PyObject * -pa_get_stream_cpu_load(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_stream_cpu_load(PyObject *self, PyObject *args) { + double cpuload; PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; - if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) + if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) { return NULL; + } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - stream = streamObject->stream; - return PyFloat_FromDouble(Pa_GetStreamCpuLoad(stream)); -} + // clang-format off + Py_BEGIN_ALLOW_THREADS + cpuload = Pa_GetStreamCpuLoad(streamObject->stream); + Py_END_ALLOW_THREADS + // clang-format on + return PyFloat_FromDouble(cpuload); +} /************************************************************* * Stream Read/Write *************************************************************/ -static PyObject * -pa_write_stream(PyObject *self, PyObject *args) -{ +static PyObject *pa_write_stream(PyObject *self, PyObject *args) { const char *data; int total_size; int total_frames; @@ -2344,44 +2041,43 @@ pa_write_stream(PyObject *self, PyObject *args) PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; + // clang-format off if (!PyArg_ParseTuple(args, "O!s#i|i", - &_pyAudio_StreamType, - &stream_arg, - &data, - &total_size, - &total_frames, - &should_throw_exception)) + &_pyAudio_StreamType, + &stream_arg, + &data, + &total_size, + &total_frames, + &should_throw_exception)) { return NULL; + } + // clang-format on - /* make sure total frames is larger than 0 */ if (total_frames < 0) { - PyErr_SetString(PyExc_ValueError, - "Invalid number of frames"); + PyErr_SetString(PyExc_ValueError, "Invalid number of frames"); return NULL; } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - stream = streamObject->stream; - + // clang-format off Py_BEGIN_ALLOW_THREADS - err = Pa_WriteStream(stream, data, total_frames); + err = Pa_WriteStream(streamObject->stream, data, total_frames); Py_END_ALLOW_THREADS + // clang-format on if (err != paNoError) { if (err == paOutputUnderflowed) { - if (should_throw_exception) - goto error; + if (should_throw_exception) { + goto error; + } } else goto error; } @@ -2389,8 +2085,7 @@ pa_write_stream(PyObject *self, PyObject *args) Py_INCREF(Py_None); return Py_None; - error: - /* cleanup */ +error: _cleanup_Stream_object(streamObject); #ifdef VERBOSE @@ -2400,162 +2095,147 @@ pa_write_stream(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(err), - err)); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } -static PyObject * -pa_read_stream(PyObject *self, PyObject *args) -{ +static PyObject *pa_read_stream(PyObject *self, PyObject *args) { int err; int total_frames; short *sampleBlock; int num_bytes; - int should_warn = 0; PyObject *rv; + int should_raise_exception = 0; PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; PaStreamParameters *inputParameters; + // clang-format off if (!PyArg_ParseTuple(args, "O!i|i", - &_pyAudio_StreamType, - &stream_arg, - &total_frames, - &should_warn)) + &_pyAudio_StreamType, + &stream_arg, + &total_frames, + &should_raise_exception)) { return NULL; + } + // clang-format on - /* make sure value is positive! */ if (total_frames < 0) { PyErr_SetString(PyExc_ValueError, "Invalid number of frames"); return NULL; } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - stream = streamObject->stream; inputParameters = streamObject->inputParameters; num_bytes = (total_frames) * (inputParameters->channelCount) * - (Pa_GetSampleSize(inputParameters->sampleFormat)); + (Pa_GetSampleSize(inputParameters->sampleFormat)); #ifdef VERBOSE fprintf(stderr, "Allocating %d bytes\n", num_bytes); #endif rv = PyBytes_FromStringAndSize(NULL, num_bytes); - sampleBlock = (short *) PyBytes_AsString(rv); + sampleBlock = (short *)PyBytes_AsString(rv); if (sampleBlock == NULL) { - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Out of memory", - paInsufficientMemory)); + PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInsufficientMemory, + "Out of memory")); return NULL; } + // clang-format off Py_BEGIN_ALLOW_THREADS - err = Pa_ReadStream(stream, sampleBlock, total_frames); + err = Pa_ReadStream(streamObject->stream, sampleBlock, total_frames); Py_END_ALLOW_THREADS + // clang-format on if (err != paNoError) { + if (err == paInputOverflowed) { + if (should_raise_exception) { + goto error; + } + } else { + goto error; + } + } - /* ignore input overflow and output underflow */ - if (err & paInputOverflowed) { - - if (should_warn) - fprintf(stderr, "WARN: Received paInputOverflowed\n"); - -#ifdef VERBOSE - fprintf(stderr, "Input Overflow.\n"); -#endif + return rv; - } else if (err & paOutputUnderflowed) { +error: + _cleanup_Stream_object(streamObject); + Py_XDECREF(rv); + PyErr_SetObject(PyExc_IOError, + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); #ifdef VERBOSE - fprintf(stderr, "Output Underflow.\n"); + fprintf(stderr, "An error occured while using the portaudio stream\n"); + fprintf(stderr, "Error number: %d\n", err); + fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif - } else { - /* clean up */ - _cleanup_Stream_object(streamObject); - - /* free the string buffer */ - Py_XDECREF(rv); - - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(err), err)); - return NULL; - } - } - - return rv; + return NULL; } -static PyObject * -pa_get_stream_write_available(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_stream_write_available(PyObject *self, PyObject *args) { signed long frames; PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; - if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) + if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) { return NULL; + } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - stream = streamObject->stream; - frames = Pa_GetStreamWriteAvailable(stream); + // clang-format off + Py_BEGIN_ALLOW_THREADS + frames = Pa_GetStreamWriteAvailable(streamObject->stream); + Py_END_ALLOW_THREADS + // clang-format on + return PyLong_FromLong(frames); } -static PyObject * -pa_get_stream_read_available(PyObject *self, PyObject *args) -{ +static PyObject *pa_get_stream_read_available(PyObject *self, PyObject *args) { signed long frames; PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; - if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) + if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) { return NULL; + } - streamObject = (_pyAudio_Stream *) stream_arg; + streamObject = (_pyAudio_Stream *)stream_arg; if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - stream = streamObject->stream; - frames = Pa_GetStreamReadAvailable(stream); + // clang-format off + Py_BEGIN_ALLOW_THREADS + frames = Pa_GetStreamReadAvailable(streamObject->stream); + Py_END_ALLOW_THREADS + // clang-format on + return PyLong_FromLong(frames); } - /************************************************************ * * IV. Python Module Init @@ -2569,17 +2249,16 @@ pa_get_stream_read_available(PyObject *self, PyObject *args) #endif #if PY_MAJOR_VERSION >= 3 -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "_portaudio", - NULL, - -1, - paMethods, - NULL, - NULL, - NULL, - NULL -}; +static struct PyModuleDef moduledef = { // + PyModuleDef_HEAD_INIT, + "_portaudio", + NULL, + -1, + paMethods, + NULL, + NULL, + NULL, + NULL}; #endif PyMODINIT_FUNC @@ -2589,26 +2268,30 @@ PyInit__portaudio(void) init_portaudio(void) #endif { - PyObject* m; + PyObject *m; PyEval_InitThreads(); _pyAudio_StreamType.tp_new = PyType_GenericNew; - if (PyType_Ready(&_pyAudio_StreamType) < 0) + if (PyType_Ready(&_pyAudio_StreamType) < 0) { return ERROR_INIT; + } _pyAudio_paDeviceInfoType.tp_new = PyType_GenericNew; - if (PyType_Ready(&_pyAudio_paDeviceInfoType) < 0) + if (PyType_Ready(&_pyAudio_paDeviceInfoType) < 0) { return ERROR_INIT; + } _pyAudio_paHostApiInfoType.tp_new = PyType_GenericNew; - if (PyType_Ready(&_pyAudio_paHostApiInfoType) < 0) + if (PyType_Ready(&_pyAudio_paHostApiInfoType) < 0) { return ERROR_INIT; + } #ifdef MACOSX _pyAudio_MacOSX_hostApiSpecificStreamInfoType.tp_new = PyType_GenericNew; - if (PyType_Ready(&_pyAudio_MacOSX_hostApiSpecificStreamInfoType) < 0) + if (PyType_Ready(&_pyAudio_MacOSX_hostApiSpecificStreamInfoType) < 0) { return ERROR_INIT; + } #endif #if PY_MAJOR_VERSION >= 3 @@ -2623,9 +2306,9 @@ init_portaudio(void) #ifdef MACOSX Py_INCREF(&_pyAudio_MacOSX_hostApiSpecificStreamInfoType); - PyModule_AddObject(m, "paMacCoreStreamInfo", - (PyObject *) - &_pyAudio_MacOSX_hostApiSpecificStreamInfoType); + PyModule_AddObject( + m, "paMacCoreStreamInfo", + (PyObject *)&_pyAudio_MacOSX_hostApiSpecificStreamInfoType); #endif /* Add PortAudio constants */ @@ -2659,19 +2342,16 @@ init_portaudio(void) PyModule_AddIntConstant(m, "paNoError", paNoError); PyModule_AddIntConstant(m, "paNotInitialized", paNotInitialized); PyModule_AddIntConstant(m, "paUnanticipatedHostError", - paUnanticipatedHostError); - PyModule_AddIntConstant(m, "paInvalidChannelCount", - paInvalidChannelCount); - PyModule_AddIntConstant(m, "paInvalidSampleRate", - paInvalidSampleRate); + paUnanticipatedHostError); + PyModule_AddIntConstant(m, "paInvalidChannelCount", paInvalidChannelCount); + PyModule_AddIntConstant(m, "paInvalidSampleRate", paInvalidSampleRate); PyModule_AddIntConstant(m, "paInvalidDevice", paInvalidDevice); PyModule_AddIntConstant(m, "paInvalidFlag", paInvalidFlag); PyModule_AddIntConstant(m, "paSampleFormatNotSupported", - paSampleFormatNotSupported); + paSampleFormatNotSupported); PyModule_AddIntConstant(m, "paBadIODeviceCombination", - paBadIODeviceCombination); - PyModule_AddIntConstant(m, "paInsufficientMemory", - paInsufficientMemory); + paBadIODeviceCombination); + PyModule_AddIntConstant(m, "paInsufficientMemory", paInsufficientMemory); PyModule_AddIntConstant(m, "paBufferTooBig", paBufferTooBig); PyModule_AddIntConstant(m, "paBufferTooSmall", paBufferTooSmall); PyModule_AddIntConstant(m, "paNullCallback", paNullCallback); @@ -2680,7 +2360,7 @@ init_portaudio(void) PyModule_AddIntConstant(m, "paInternalError", paInternalError); PyModule_AddIntConstant(m, "paDeviceUnavailable", paDeviceUnavailable); PyModule_AddIntConstant(m, "paIncompatibleHostApiSpecificStreamInfo", - paIncompatibleHostApiSpecificStreamInfo); + paIncompatibleHostApiSpecificStreamInfo); PyModule_AddIntConstant(m, "paStreamIsStopped", paStreamIsStopped); PyModule_AddIntConstant(m, "paStreamIsNotStopped", paStreamIsNotStopped); PyModule_AddIntConstant(m, "paInputOverflowed", paInputOverflowed); @@ -2688,15 +2368,15 @@ init_portaudio(void) PyModule_AddIntConstant(m, "paHostApiNotFound", paHostApiNotFound); PyModule_AddIntConstant(m, "paInvalidHostApi", paInvalidHostApi); PyModule_AddIntConstant(m, "paCanNotReadFromACallbackStream", - paCanNotReadFromACallbackStream); + paCanNotReadFromACallbackStream); PyModule_AddIntConstant(m, "paCanNotWriteToACallbackStream", - paCanNotWriteToACallbackStream); + paCanNotWriteToACallbackStream); PyModule_AddIntConstant(m, "paCanNotReadFromAnOutputOnlyStream", - paCanNotReadFromAnOutputOnlyStream); + paCanNotReadFromAnOutputOnlyStream); PyModule_AddIntConstant(m, "paCanNotWriteToAnInputOnlyStream", - paCanNotWriteToAnInputOnlyStream); + paCanNotWriteToAnInputOnlyStream); PyModule_AddIntConstant(m, "paIncompatibleStreamHostApi", - paIncompatibleStreamHostApi); + paIncompatibleStreamHostApi); /* callback constants */ PyModule_AddIntConstant(m, "paContinue", paContinue); @@ -2712,27 +2392,24 @@ init_portaudio(void) #ifdef MACOSX PyModule_AddIntConstant(m, "paMacCoreChangeDeviceParameters", - paMacCoreChangeDeviceParameters); + paMacCoreChangeDeviceParameters); PyModule_AddIntConstant(m, "paMacCoreFailIfConversionRequired", - paMacCoreFailIfConversionRequired); + paMacCoreFailIfConversionRequired); PyModule_AddIntConstant(m, "paMacCoreConversionQualityMin", - paMacCoreConversionQualityMin); + paMacCoreConversionQualityMin); PyModule_AddIntConstant(m, "paMacCoreConversionQualityMedium", - paMacCoreConversionQualityMedium); + paMacCoreConversionQualityMedium); PyModule_AddIntConstant(m, "paMacCoreConversionQualityLow", - paMacCoreConversionQualityLow); + paMacCoreConversionQualityLow); PyModule_AddIntConstant(m, "paMacCoreConversionQualityHigh", - paMacCoreConversionQualityHigh); + paMacCoreConversionQualityHigh); PyModule_AddIntConstant(m, "paMacCoreConversionQualityMax", - paMacCoreConversionQualityMax); - PyModule_AddIntConstant(m, "paMacCorePlayNice", - paMacCorePlayNice); - PyModule_AddIntConstant(m, "paMacCorePro", - paMacCorePro); + paMacCoreConversionQualityMax); + PyModule_AddIntConstant(m, "paMacCorePlayNice", paMacCorePlayNice); + PyModule_AddIntConstant(m, "paMacCorePro", paMacCorePro); PyModule_AddIntConstant(m, "paMacCoreMinimizeCPUButPlayNice", - paMacCoreMinimizeCPUButPlayNice); - PyModule_AddIntConstant(m, "paMacCoreMinimizeCPU", - paMacCoreMinimizeCPU); + paMacCoreMinimizeCPUButPlayNice); + PyModule_AddIntConstant(m, "paMacCoreMinimizeCPU", paMacCoreMinimizeCPU); #endif #if PY_MAJOR_VERSION >= 3 diff --git a/src/_portaudiomodule.h b/src/_portaudiomodule.h index e8153c9..ecd3752 100644 --- a/src/_portaudiomodule.h +++ b/src/_portaudiomodule.h @@ -3,7 +3,7 @@ * * PyAudio : API Header File * - * Copyright (c) 2006-2012 Hubert Pham + * Copyright (c) 2006 Hubert Pham * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation diff --git a/src/pyaudio.py b/src/pyaudio.py index c0a6411..d4c3f2d 100644 --- a/src/pyaudio.py +++ b/src/pyaudio.py @@ -1,6 +1,6 @@ # PyAudio : Python Bindings for PortAudio. -# Copyright (c) 2006-2012 Hubert Pham +# Copyright (c) 2006 Hubert Pham # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -106,7 +106,7 @@ """ __author__ = "Hubert Pham" -__version__ = "0.2.8.1" +__version__ = "0.2.11" __docformat__ = "restructuredtext en" import sys @@ -115,9 +115,8 @@ try: import _portaudio as pa except ImportError: - print("Please build and install the PortAudio Python " + - "bindings first.") - sys.exit(-1) + print("Could not import the PyAudio C module '_portaudio'.") + raise ############################################################ # GLOBALS @@ -472,7 +471,7 @@ def get_input_latency(self): def get_output_latency(self): """ - Return the input latency. + Return the output latency. :rtype: float """ @@ -562,7 +561,7 @@ def write(self, frames, num_frames=None, Defaults to None, in which this value will be automatically computed. :param exception_on_underflow: - Specifies whether an exception should be thrown + Specifies whether an IOError exception should be thrown (or silently ignored) on buffer underflow. Defaults to False for improved performance, especially on slower platforms. @@ -587,20 +586,18 @@ def write(self, frames, num_frames=None, exception_on_underflow) - def read(self, num_frames, should_warn=False): + def read(self, num_frames, exception_on_overflow=True): """ Read samples from the stream. Do not call when using *non-blocking* mode. - :param num_frames: - The number of frames to read. - :param should_warn: - Specifies whether a warning should be written to stderr (or silently - ignored) on buffer overflow. Defaults to False. - + :param num_frames: The number of frames to read. + :param exception_on_overflow: + Specifies whether an IOError exception should be thrown + (or silently ignored) on input buffer overflow. Defaults + to True. :raises IOError: if stream is not an input stream or if the read operation was unsuccessful. - :rtype: string """ @@ -608,7 +605,7 @@ def read(self, num_frames, should_warn=False): raise IOError("Not input stream", paCanNotReadFromAnOutputOnlyStream) - return pa.read_stream(self._stream, num_frames, should_warn) + return pa.read_stream(self._stream, num_frames, exception_on_overflow) def get_read_available(self): """ diff --git a/tests/error_tests.py b/tests/error_tests.py new file mode 100644 index 0000000..6672139 --- /dev/null +++ b/tests/error_tests.py @@ -0,0 +1,120 @@ +import sys +import time +import unittest + +import pyaudio + +class PyAudioErrorTests(unittest.TestCase): + def setUp(self): + self.p = pyaudio.PyAudio() + + def tearDown(self): + self.p.terminate() + + def test_invalid_sample_size(self): + with self.assertRaises(ValueError): + self.p.get_sample_size(10) + + def test_invalid_width(self): + with self.assertRaises(ValueError): + self.p.get_format_from_width(8) + + def test_invalid_device(self): + with self.assertRaises(IOError): + self.p.get_host_api_info_by_type(-1) + + def test_invalid_hostapi(self): + with self.assertRaises(IOError): + self.p.get_host_api_info_by_index(-1) + + def test_invalid_host_api_devinfo(self): + with self.assertRaises(IOError): + self.p.get_device_info_by_host_api_device_index(0, -1) + + with self.assertRaises(IOError): + self.p.get_device_info_by_host_api_device_index(-1, 0) + + def test_invalid_device_devinfo(self): + with self.assertRaises(IOError): + self.p.get_device_info_by_index(-1) + + def test_error_without_stream_start(self): + with self.assertRaises(IOError): + stream = self.p.open(channels=1, + rate=44100, + format=pyaudio.paInt16, + input=True, + start=False) # not starting stream + stream.read(2) + + def test_error_writing_to_readonly_stream(self): + with self.assertRaises(IOError): + stream = self.p.open(channels=1, + rate=44100, + format=pyaudio.paInt16, + input=True) + stream.write('foo') + + def test_error_negative_frames(self): + with self.assertRaises(ValueError): + stream = self.p.open(channels=1, + rate=44100, + format=pyaudio.paInt16, + input=True) + stream.read(-1) + + def test_invalid_attr_on_closed_stream(self): + stream = self.p.open(channels=1, + rate=44100, + format=pyaudio.paInt16, + input=True) + stream.close() + with self.assertRaises(IOError): + stream.get_input_latency() + with self.assertRaises(IOError): + stream.read(1) + + def test_invalid_format_supported(self): + with self.assertRaises(ValueError): + self.p.is_format_supported(8000, -1, 1, pyaudio.paInt16) + + with self.assertRaises(ValueError): + self.p.is_format_supported(8000, 0, -1, pyaudio.paInt16) + + def test_write_underflow_exception(self): + stream = self.p.open(channels=1, + rate=44100, + format=pyaudio.paInt16, + output=True) + time.sleep(0.5) + stream.write('\x00\x00\x00\x00', exception_on_underflow=False) + + # It's difficult to invoke an underflow on ALSA, so skip. + if sys.platform in ('linux', 'linux2'): + return + + with self.assertRaises(IOError) as err: + time.sleep(0.5) + stream.write('\x00\x00\x00\x00', exception_on_underflow=True) + + self.assertEqual(err.exception.errno, pyaudio.paOutputUnderflowed) + self.assertEqual(err.exception.strerror, 'Output underflowed') + + def test_read_overflow_exception(self): + stream = self.p.open(channels=1, + rate=44100, + format=pyaudio.paInt16, + input=True) + time.sleep(0.5) + stream.read(2, exception_on_overflow=False) + + # It's difficult to invoke an underflow on ALSA, so skip. + if sys.platform in ('linux', 'linux2'): + return + + with self.assertRaises(IOError) as err: + time.sleep(0.5) + stream.read(2, exception_on_overflow=True) + + self.assertEqual(err.exception.errno, pyaudio.paInputOverflowed) + self.assertEqual(err.exception.strerror, 'Input overflowed') diff --git a/tests/pyaudio_tests.py b/tests/pyaudio_tests.py new file mode 100644 index 0000000..1655a63 --- /dev/null +++ b/tests/pyaudio_tests.py @@ -0,0 +1,642 @@ +# -*- coding: utf-8 -*- +"""Automated unit tests for testing audio playback and capture. + +These tests require an OS loopback sound device that forwards audio +output, generated by PyAudio for playback, and forwards it to an input +device, which PyAudio can record and verify against a test signal. + +On Mac OS X, Soundflower can create such a device. + +On GNU/Linux, the snd-aloop kernel module provides a loopback ALSA +device. Use examples/system_info.py to identify the name of the loopback +device. +""" + +import math +import struct +import time +import unittest +import wave +import sys + +import numpy + +import pyaudio + +DUMP_CAPTURE=False + +class PyAudioTests(unittest.TestCase): + def setUp(self): + self.p = pyaudio.PyAudio() + (self.loopback_input_idx, + self.loopback_output_idx) = self.get_audio_loopback() + assert (self.loopback_input_idx is None + or self.loopback_input_idx >= 0), "No loopback device found" + assert (self.loopback_output_idx is None + or self.loopback_output_idx >= 0), "No loopback device found" + + def tearDown(self): + self.p.terminate() + + def get_audio_loopback(self): + if sys.platform == 'darwin': + return self._find_audio_loopback( + 'Soundflower (2ch)', 'Soundflower (2ch)') + if sys.platform in ('linux', 'linux2'): + return self._find_audio_loopback( + 'Loopback: PCM (hw:1,0)', 'Loopback: PCM (hw:1,1)') + if sys.platform == 'win32': + # Assumes running in a VM, in which the hypervisor can + # set up a loopback device to back the "default" audio devices. + # Here, None indicates default device. + return None, None + + return -1, -1 + + def _find_audio_loopback(self, indev, outdev): + """Utility to find audio loopback device.""" + input_idx, output_idx = -1, -1 + for device_idx in range(self.p.get_device_count()): + devinfo = self.p.get_device_info_by_index(device_idx) + if (outdev == devinfo.get('name') and + devinfo.get('maxOutputChannels', 0) > 0): + output_idx = device_idx + + if (indev == devinfo.get('name') and + devinfo.get('maxInputChannels', 0) > 0): + input_idx = device_idx + + if output_idx > -1 and input_idx > -1: + break + + return input_idx, output_idx + + def test_system_info(self): + """Basic system info tests""" + self.assertTrue(self.p.get_host_api_count() > 0) + self.assertTrue(self.p.get_device_count() > 0) + api_info = self.p.get_host_api_info_by_index(0) + self.assertTrue(len(api_info.items()) > 0) + + def test_input_output_blocking(self): + """Test blocking-based record and playback.""" + rate = 44100 # frames per second + width = 2 # bytes per sample + channels = 2 + # Blocking-mode might add some initial choppiness on some + # platforms/loopback devices, so set a longer duration. + duration = 3 # seconds + frames_per_chunk = 1024 + + freqs = [130.81, 329.63, 440.0, 466.16, 587.33, 739.99] + test_signal = self.create_reference_signal(freqs, rate, width, duration) + audio_chunks = self.signal_to_chunks( + test_signal, frames_per_chunk, channels) + + out_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + output=True, + frames_per_buffer=frames_per_chunk, + output_device_index=self.loopback_output_idx) + in_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + input=True, + frames_per_buffer=frames_per_chunk, + input_device_index=self.loopback_input_idx) + + captured = [] + for chunk in audio_chunks: + out_stream.write(chunk) + captured.append(in_stream.read(frames_per_chunk)) + # Capture a few more frames, since there is some lag. + for i in range(8): + captured.append(in_stream.read(frames_per_chunk)) + + in_stream.stop_stream() + out_stream.stop_stream() + + if DUMP_CAPTURE: + self.write_wav('test_blocking.wav', b''.join(captured), + width, channels, rate) + + captured_signal = self.pcm16_to_numpy(b''.join(captured)) + captured_left_channel = captured_signal[::2] + captured_right_channel = captured_signal[1::2] + + self.assert_pcm16_spectrum_nearly_equal( + rate, + captured_left_channel, + test_signal, + len(freqs)) + self.assert_pcm16_spectrum_nearly_equal( + rate, + captured_right_channel, + test_signal, + len(freqs)) + + def test_input_output_callback(self): + """Test callback-based record and playback.""" + rate = 44100 # frames per second + width = 2 # bytes per sample + channels = 2 + duration = 1 # second + frames_per_chunk = 1024 + + freqs = [130.81, 329.63, 440.0, 466.16, 587.33, 739.99] + test_signal = self.create_reference_signal(freqs, rate, width, duration) + audio_chunks = self.signal_to_chunks( + test_signal, frames_per_chunk, channels) + + state = {'count': 0} + def out_callback(_, frame_count, time_info, status): + if state['count'] >= len(audio_chunks): + return ('', pyaudio.paComplete) + rval = (audio_chunks[state['count']], pyaudio.paContinue) + state['count'] += 1 + return rval + + captured = [] + def in_callback(in_data, frame_count, time_info, status): + captured.append(in_data) + return (None, pyaudio.paContinue) + + out_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + output=True, + frames_per_buffer=frames_per_chunk, + output_device_index=self.loopback_output_idx, + stream_callback=out_callback) + + in_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + input=True, + frames_per_buffer=frames_per_chunk, + input_device_index=self.loopback_input_idx, + stream_callback=in_callback) + + in_stream.start_stream() + out_stream.start_stream() + time.sleep(duration + 1) + in_stream.stop_stream() + out_stream.stop_stream() + + if DUMP_CAPTURE: + self.write_wav('test_callback.wav', b''.join(captured), + width, channels, rate) + + captured_signal = self.pcm16_to_numpy(b''.join(captured)) + captured_left_channel = captured_signal[::2] + captured_right_channel = captured_signal[1::2] + + self.assert_pcm16_spectrum_nearly_equal( + rate, + captured_left_channel, + test_signal, + len(freqs)) + self.assert_pcm16_spectrum_nearly_equal( + rate, + captured_right_channel, + test_signal, + len(freqs)) + + def test_device_lock_gil_order(self): + """Ensure no deadlock between Pa_{Open,Start,Stop}Stream and GIL.""" + # This test targets OSX/macOS CoreAudio, which seems to use + # audio device locks. On ALSA and Win32 MME, this problem + # doesn't seem to appear despite not releasing the GIL when + # calling into PortAudio. + rate = 44100 # frames per second + width = 2 # bytes per sample + channels = 2 + frames_per_chunk = 1024 + + def out_callback(_, frame_count, time_info, status): + return ('', pyaudio.paComplete) + + def in_callback(in_data, frame_count, time_info, status): + # Release the GIL for a bit + time.sleep(2) + return (None, pyaudio.paComplete) + + in_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + input=True, + start=False, + frames_per_buffer=frames_per_chunk, + input_device_index=self.loopback_input_idx, + stream_callback=in_callback) + # In a separate (C) thread, portaudio/driver will grab the device lock, + # then the GIL to call in_callback. + in_stream.start_stream() + # Wait a bit to let that callback thread start. + time.sleep(1) + # in_callback will eventually drop the GIL when executing + # time.sleep (while retaining the device lock), allowing the + # following code to run. Opening a stream and starting it MUST + # release the GIL before attempting to acquire the device + # lock. Otherwise, the following code will wait for the device + # lock (while holding the GIL), while the in_callback thread + # will be waiting for the GIL once time.sleep completes (while + # holding the device lock), leading to deadlock. + out_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + output=True, + frames_per_buffer=frames_per_chunk, + output_device_index=self.loopback_output_idx, + stream_callback=out_callback) + out_stream.start_stream() + + time.sleep(0.1) + in_stream.stop_stream() + out_stream.stop_stream() + + def test_stream_state_gil(self): + """Ensure no deadlock between Pa_IsStream{Active,Stopped} and GIL.""" + rate = 44100 # frames per second + width = 2 # bytes per sample + channels = 2 + frames_per_chunk = 1024 + + def out_callback(_, frame_count, time_info, status): + return ('', pyaudio.paComplete) + + def in_callback(in_data, frame_count, time_info, status): + # Release the GIL for a bit + time.sleep(2) + return (None, pyaudio.paComplete) + + in_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + input=True, + start=False, + frames_per_buffer=frames_per_chunk, + input_device_index=self.loopback_input_idx, + stream_callback=in_callback) + out_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + output=True, + start=False, + frames_per_buffer=frames_per_chunk, + output_device_index=self.loopback_output_idx, + stream_callback=out_callback) + # In a separate (C) thread, portaudio/driver will grab the device lock, + # then the GIL to call in_callback. + in_stream.start_stream() + # Wait a bit to let that callback thread start. + time.sleep(1) + # in_callback will eventually drop the GIL when executing + # time.sleep (while retaining the device lock), allowing the + # following code to run. Checking the state of the stream MUST + # not require the device lock, but if it does, it must release the GIL + # before attempting to acquire the device + # lock. Otherwise, the following code will wait for the device + # lock (while holding the GIL), while the in_callback thread + # will be waiting for the GIL once time.sleep completes (while + # holding the device lock), leading to deadlock. + self.assertTrue(in_stream.is_active()) + self.assertFalse(in_stream.is_stopped()) + + self.assertTrue(out_stream.is_stopped()) + self.assertFalse(out_stream.is_active()) + out_stream.start_stream() + self.assertFalse(out_stream.is_stopped()) + self.assertTrue(out_stream.is_active()) + + time.sleep(0.1) + in_stream.stop_stream() + out_stream.stop_stream() + + def test_get_stream_time_gil(self): + """Ensure no deadlock between PA_GetStreamTime and GIL.""" + rate = 44100 # frames per second + width = 2 # bytes per sample + channels = 2 + frames_per_chunk = 1024 + + def out_callback(_, frame_count, time_info, status): + return ('', pyaudio.paComplete) + + def in_callback(in_data, frame_count, time_info, status): + # Release the GIL for a bit + time.sleep(2) + return (None, pyaudio.paComplete) + + in_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + input=True, + start=False, + frames_per_buffer=frames_per_chunk, + input_device_index=self.loopback_input_idx, + stream_callback=in_callback) + out_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + output=True, + start=False, + frames_per_buffer=frames_per_chunk, + output_device_index=self.loopback_output_idx, + stream_callback=out_callback) + # In a separate (C) thread, portaudio/driver will grab the device lock, + # then the GIL to call in_callback. + in_stream.start_stream() + # Wait a bit to let that callback thread start. + time.sleep(1) + # in_callback will eventually drop the GIL when executing + # time.sleep (while retaining the device lock), allowing the + # following code to run. Getting the stream time MUST not + # require the device lock, but if it does, it must release the + # GIL before attempting to acquire the device lock. Otherwise, + # the following code will wait for the device lock (while + # holding the GIL), while the in_callback thread will be + # waiting for the GIL once time.sleep completes (while holding + # the device lock), leading to deadlock. + self.assertGreater(in_stream.get_time(), -1) + self.assertGreater(out_stream.get_time(), 1) + + time.sleep(0.1) + in_stream.stop_stream() + out_stream.stop_stream() + + def test_get_stream_cpuload_gil(self): + """Ensure no deadlock between Pa_GetStreamCpuLoad and GIL.""" + rate = 44100 # frames per second + width = 2 # bytes per sample + channels = 2 + frames_per_chunk = 1024 + + def out_callback(_, frame_count, time_info, status): + return ('', pyaudio.paComplete) + + def in_callback(in_data, frame_count, time_info, status): + # Release the GIL for a bit + time.sleep(2) + return (None, pyaudio.paComplete) + + in_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + input=True, + start=False, + frames_per_buffer=frames_per_chunk, + input_device_index=self.loopback_input_idx, + stream_callback=in_callback) + out_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + output=True, + start=False, + frames_per_buffer=frames_per_chunk, + output_device_index=self.loopback_output_idx, + stream_callback=out_callback) + # In a separate (C) thread, portaudio/driver will grab the device lock, + # then the GIL to call in_callback. + in_stream.start_stream() + # Wait a bit to let that callback thread start. + time.sleep(1) + # in_callback will eventually drop the GIL when executing + # time.sleep (while retaining the device lock), allowing the + # following code to run. Getting the stream cpuload MUST not + # require the device lock, but if it does, it must release the + # GIL before attempting to acquire the device lock. Otherwise, + # the following code will wait for the device lock (while + # holding the GIL), while the in_callback thread will be + # waiting for the GIL once time.sleep completes (while holding + # the device lock), leading to deadlock. + self.assertGreater(in_stream.get_cpu_load(), -1) + self.assertGreater(out_stream.get_cpu_load(), -1) + + time.sleep(0.1) + in_stream.stop_stream() + out_stream.stop_stream() + + def test_get_stream_write_available_gil(self): + """Ensure no deadlock between Pa_GetStreamWriteAvailable and GIL.""" + rate = 44100 # frames per second + width = 2 # bytes per sample + channels = 2 + frames_per_chunk = 1024 + + def in_callback(in_data, frame_count, time_info, status): + # Release the GIL for a bit + time.sleep(2) + return (None, pyaudio.paComplete) + + in_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + input=True, + start=False, + frames_per_buffer=frames_per_chunk, + input_device_index=self.loopback_input_idx, + stream_callback=in_callback) + out_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + output=True, + frames_per_buffer=frames_per_chunk, + output_device_index=self.loopback_output_idx) + # In a separate (C) thread, portaudio/driver will grab the device lock, + # then the GIL to call in_callback. + in_stream.start_stream() + # Wait a bit to let that callback thread start. + time.sleep(1) + # in_callback will eventually drop the GIL when executing + # time.sleep (while retaining the device lock), allowing the + # following code to run. Getting the stream write available MUST not + # require the device lock, but if it does, it must release the + # GIL before attempting to acquire the device lock. Otherwise, + # the following code will wait for the device lock (while + # holding the GIL), while the in_callback thread will be + # waiting for the GIL once time.sleep completes (while holding + # the device lock), leading to deadlock. + self.assertGreater(out_stream.get_write_available(), -1) + + time.sleep(0.1) + in_stream.stop_stream() + + def test_get_stream_read_available_gil(self): + """Ensure no deadlock between Pa_GetStreamReadAvailable and GIL.""" + rate = 44100 # frames per second + width = 2 # bytes per sample + channels = 2 + frames_per_chunk = 1024 + + def out_callback(in_data, frame_count, time_info, status): + # Release the GIL for a bit + time.sleep(2) + return (None, pyaudio.paComplete) + + in_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + input=True, + frames_per_buffer=frames_per_chunk, + input_device_index=self.loopback_input_idx) + out_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + output=True, + start=False, + frames_per_buffer=frames_per_chunk, + output_device_index=self.loopback_output_idx, + stream_callback=out_callback) + # In a separate (C) thread, portaudio/driver will grab the device lock, + # then the GIL to call in_callback. + out_stream.start_stream() + # Wait a bit to let that callback thread start. + time.sleep(1) + # in_callback will eventually drop the GIL when executing + # time.sleep (while retaining the device lock), allowing the + # following code to run. Getting the stream read available MUST not + # require the device lock, but if it does, it must release the + # GIL before attempting to acquire the device lock. Otherwise, + # the following code will wait for the device lock (while + # holding the GIL), while the in_callback thread will be + # waiting for the GIL once time.sleep completes (while holding + # the device lock), leading to deadlock. + self.assertGreater(in_stream.get_read_available(), -1) + + time.sleep(0.1) + in_stream.stop_stream() + + def test_terminate_gil(self): + """Ensure no deadlock between Pa_Terminate and GIL.""" + rate = 44100 # frames per second + width = 2 # bytes per sample + channels = 2 + frames_per_chunk = 1024 + + def out_callback(in_data, frame_count, time_info, status): + # Release the GIL for a bit + time.sleep(2) + return (None, pyaudio.paComplete) + + out_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + output=True, + start=False, + frames_per_buffer=frames_per_chunk, + output_device_index=self.loopback_output_idx, + stream_callback=out_callback) + # In a separate (C) thread, portaudio/driver will grab the device lock, + # then the GIL to call in_callback. + out_stream.start_stream() + # Wait a bit to let that callback thread start. + time.sleep(1) + # in_callback will eventually drop the GIL when executing + # time.sleep (while retaining the device lock), allowing the + # following code to run. Terminating PyAudio MUST not + # require the device lock, but if it does, it must release the + # GIL before attempting to acquire the device lock. Otherwise, + # the following code will wait for the device lock (while + # holding the GIL), while the in_callback thread will be + # waiting for the GIL once time.sleep completes (while holding + # the device lock), leading to deadlock. + self.p.terminate() + + @staticmethod + def create_reference_signal(freqs, sampling_rate, width, duration): + """Return reference signal with several sinuoids with frequencies + specified by freqs.""" + total_frames = int(sampling_rate * duration) + max_amp = float(2**(width * 8 - 1) - 1) + avg_amp = max_amp / len(freqs) + return [ + int(sum(avg_amp * math.sin(2*math.pi*freq*(k/float(sampling_rate))) + for freq in freqs)) + for k in range(total_frames)] + + @staticmethod + def signal_to_chunks(frame_data, frames_per_chunk, channels): + """Given an array of values comprising the signal, return an iterable + of binary chunks, with each chunk containing frames_per_chunk + frames. Each frame represents a single value from the signal, + duplicated for each channel specified by channels. + """ + frames = [struct.pack('h', x) * channels for x in frame_data] + # Chop up frames into chunks + return [b''.join(chunk_frames) for chunk_frames in + tuple(frames[i:i+frames_per_chunk] + for i in range(0, len(frames), frames_per_chunk))] + + @staticmethod + def pcm16_to_numpy(bytestring): + """From PCM 16-bit bytes, return an equivalent numpy array of values.""" + return struct.unpack('%dh' % (len(bytestring) / 2), bytestring) + + @staticmethod + def write_wav(filename, data, width, channels, rate): + """Write PCM data to wave file.""" + wf = wave.open(filename, 'wb') + wf.setnchannels(channels) + wf.setsampwidth(width) + wf.setframerate(rate) + wf.writeframes(data) + wf.close() + + def assert_pcm16_spectrum_nearly_equal(self, sampling_rate, cap, ref, + num_freq_peaks_expected): + """Compares the discrete fourier transform of a captured signal + against the reference signal and ensures that the frequency peaks + match.""" + # When passing a reference signal through the loopback device, + # the captured signal may include additional noise, as well as + # time lag, so testing that the captured signal is "similar + # enough" to the reference using bit-wise equality won't work + # well. Instead, the approach here a) assumes the reference + # signal is a sum of sinusoids and b) computes the discrete + # fourier transform of the reference and captured signals, and + # ensures that the frequencies of the top + # num_freq_peaks_expected frequency peaks are close. + cap_fft = numpy.absolute(numpy.fft.rfft(cap)) + ref_fft = numpy.absolute(numpy.fft.rfft(ref)) + # Find the indices of the peaks: + cap_peak_indices = sorted(numpy.argpartition( + cap_fft, -num_freq_peaks_expected)[-num_freq_peaks_expected:]) + ref_peak_indices = sorted(numpy.argpartition( + ref_fft, -num_freq_peaks_expected)[-num_freq_peaks_expected:]) + # Ensure that the corresponding frequencies of the peaks are close: + for cap_freq_index, ref_freq_index in zip(cap_peak_indices, + ref_peak_indices): + cap_freq = cap_freq_index / float(len(cap)) * (sampling_rate / 2) + ref_freq = ref_freq_index / float(len(ref)) * (sampling_rate / 2) + diff = abs(cap_freq - ref_freq) + self.assertLess(diff, 1.0) + + # As an additional test, verify that the spectrum (not just + # the peaks) of the reference and captured signal are similar + # by computing the cross-correlation of the spectra. Assuming they + # are nearly identical, the cross-correlation should contain a large + # peak when the spectra overlap and mostly 0s elsewhere. Verify that + # using a histogram of the cross-correlation: + freq_corr_hist, _ = numpy.histogram( + numpy.correlate(cap_fft, ref_fft, mode='full'), + bins=10) + self.assertLess(sum(freq_corr_hist[2:])/sum(freq_corr_hist), 1e-2)