From 53ab5eb107f9aacb443028f745b84cb97c8a793a Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Mon, 21 Sep 2015 23:26:28 -0700 Subject: [PATCH 01/32] Fix overflow error handling logic for pa_read_stream. This adds an optional parameter specifying whether an input overflow exception should be raised (or ignored). Previously, a code comment conflicted with actual implementation, by suggesting that overflows are ignored when in fact they are always surfaced. Furthermore, detecting an overflow condition was flawed. This change allows the user to decide whether exceptions are thrown, for API parity with pa_write_stream. Thanks to Tony Jacobson for the report and patch! --- src/_portaudiomodule.c | 50 ++++++++++++++++++++++-------------------- src/pyaudio.py | 10 ++++++--- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/_portaudiomodule.c b/src/_portaudiomodule.c index a8f053d..25cac9a 100644 --- a/src/_portaudiomodule.c +++ b/src/_portaudiomodule.c @@ -2414,16 +2414,18 @@ pa_read_stream(PyObject *self, PyObject *args) short *sampleBlock; int num_bytes; PyObject *rv; + int should_raise_exception = 0; PyObject *stream_arg; _pyAudio_Stream *streamObject; PaStream *stream; PaStreamParameters *inputParameters; - if (!PyArg_ParseTuple(args, "O!i", + if (!PyArg_ParseTuple(args, "O!i|i", &_pyAudio_StreamType, &stream_arg, - &total_frames)) + &total_frames, + &should_raise_exception)) return NULL; /* make sure value is positive! */ @@ -2467,35 +2469,35 @@ pa_read_stream(PyObject *self, PyObject *args) Py_END_ALLOW_THREADS if (err != paNoError) { + if (err == paInputOverflowed) { + if (should_raise_exception) { + goto error; + } + } else { + goto error; + } + } - /* ignore input overflow and output underflow */ - if (err & paInputOverflowed) { + return rv; -#ifdef VERBOSE - fprintf(stderr, "Input Overflow.\n"); -#endif + error: + /* clean up */ + _cleanup_Stream_object(streamObject); - } else if (err & paOutputUnderflowed) { + /* free the string buffer */ + 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 * diff --git a/src/pyaudio.py b/src/pyaudio.py index 73c4bbc..df56367 100644 --- a/src/pyaudio.py +++ b/src/pyaudio.py @@ -562,7 +562,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,12 +587,16 @@ def write(self, frames, num_frames=None, exception_on_underflow) - def read(self, num_frames): + 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 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 @@ -602,7 +606,7 @@ def read(self, num_frames): raise IOError("Not input stream", paCanNotReadFromAnOutputOnlyStream) - return pa.read_stream(self._stream, num_frames) + return pa.read_stream(self._stream, num_frames, exception_on_overflow) def get_read_available(self): """ From 1783aaf9bcc6f8bffc478cb5120ccb6f5091b3fb Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Mon, 21 Sep 2015 23:49:51 -0700 Subject: [PATCH 02/32] Fix IOError arguments. Previously IOErrors raised had strerror and errno arguments swapped, which this change fixes. Thanks to Sami Liedes for the report! --- src/_portaudiomodule.c | 198 +++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 106 deletions(-) diff --git a/src/_portaudiomodule.c b/src/_portaudiomodule.c index 25cac9a..e7ddee9 100644 --- a/src/_portaudiomodule.c +++ b/src/_portaudiomodule.c @@ -984,17 +984,16 @@ _pyAudio_Stream_get_structVersion(_pyAudio_Stream *self, /* sanity check */ 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)); + Py_BuildValue("(i,s)", + paBadStreamPtr, + "No StreamInfo available")); return NULL; } @@ -1008,18 +1007,17 @@ _pyAudio_Stream_get_inputLatency(_pyAudio_Stream *self, /* sanity check */ 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)); + Py_BuildValue("(i,s)", + paBadStreamPtr, + "No StreamInfo available")); return NULL; } @@ -1033,18 +1031,17 @@ _pyAudio_Stream_get_outputLatency(_pyAudio_Stream *self, /* sanity check */ 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)); + Py_BuildValue("(i,s)", + paBadStreamPtr, + "No StreamInfo available")); return NULL; } @@ -1058,18 +1055,17 @@ _pyAudio_Stream_get_sampleRate(_pyAudio_Stream *self, /* sanity check */ 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)); + Py_BuildValue("(i,s)", + paBadStreamPtr, + "No StreamInfo available")); return NULL; } @@ -1213,8 +1209,8 @@ pa_initialize(PyObject *self, PyObject *args) 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; } @@ -1253,8 +1249,8 @@ 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; } @@ -1280,8 +1276,8 @@ 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; } @@ -1308,8 +1304,8 @@ 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; } @@ -1337,8 +1333,9 @@ pa_host_api_device_index_to_device_index(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - Pa_GetErrorText(devIndex), devIndex)); + Py_BuildValue("(i,s)", + devIndex, + Pa_GetErrorText(devIndex))); return NULL; } @@ -1359,9 +1356,9 @@ pa_get_host_api_info(PyObject *self, PyObject *args) if (!_info) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Invalid host api info", - paInvalidHostApi)); + Py_BuildValue("(i,s)", + paInvalidHostApi, + "Invalid host api info")); return NULL; } @@ -1393,8 +1390,8 @@ 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; } @@ -1422,8 +1419,8 @@ 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; } @@ -1451,8 +1448,8 @@ 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; } @@ -1473,8 +1470,9 @@ pa_get_device_info(PyObject *self, PyObject *args) if (!_info) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Invalid device info", paInvalidDevice)); + Py_BuildValue("(i,s)", + paInvalidDevice, + "Invalid device info")); return NULL; } @@ -1807,10 +1805,10 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) 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; } @@ -1844,10 +1842,10 @@ 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; } @@ -1901,8 +1899,8 @@ 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; } @@ -1910,9 +1908,10 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) 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; } @@ -2057,9 +2056,9 @@ pa_start_stream(PyObject *self, PyObject *args) if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", + paBadStreamPtr, + "Stream closed")); return NULL; } @@ -2076,9 +2075,8 @@ 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; } @@ -2121,9 +2119,8 @@ 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; } @@ -2165,9 +2162,8 @@ 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; } @@ -2190,9 +2186,8 @@ pa_is_stream_stopped(PyObject *self, PyObject *args) if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", + paBadStreamPtr, "Stream closed")); return NULL; } @@ -2208,9 +2203,8 @@ 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; } @@ -2254,9 +2248,8 @@ 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; } @@ -2284,9 +2277,8 @@ pa_get_stream_time(PyObject *self, PyObject *args) if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", + paBadStreamPtr, "Stream closed")); return NULL; } @@ -2295,9 +2287,8 @@ pa_get_stream_time(PyObject *self, PyObject *args) if ((time = Pa_GetStreamTime(stream)) == 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; } @@ -2318,9 +2309,8 @@ pa_get_stream_cpu_load(PyObject *self, PyObject *args) if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", + paBadStreamPtr, "Stream closed")); return NULL; } @@ -2366,9 +2356,8 @@ pa_write_stream(PyObject *self, PyObject *args) if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", + paBadStreamPtr, "Stream closed")); return NULL; } @@ -2400,9 +2389,9 @@ 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; } @@ -2438,9 +2427,8 @@ pa_read_stream(PyObject *self, PyObject *args) if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", + paBadStreamPtr, "Stream closed")); return NULL; } @@ -2458,9 +2446,9 @@ pa_read_stream(PyObject *self, PyObject *args) if (sampleBlock == NULL) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Out of memory", - paInsufficientMemory)); + Py_BuildValue("(i,s)", + paInsufficientMemory, + "Out of memory")); return NULL; } @@ -2515,9 +2503,8 @@ pa_get_stream_write_available(PyObject *self, PyObject *args) if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", + paBadStreamPtr, "Stream closed")); return NULL; } @@ -2541,9 +2528,8 @@ pa_get_stream_read_available(PyObject *self, PyObject *args) if (!_is_open(streamObject)) { PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(s,i)", - "Stream closed", - paBadStreamPtr)); + Py_BuildValue("(i,s)", + paBadStreamPtr, "Stream closed")); return NULL; } From ebea06b12c3a73cb3d6d08e2629c81f6e44297d9 Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sat, 17 Oct 2015 12:17:10 -0700 Subject: [PATCH 03/32] On C module import error, raise the exception rather than sys.exit. --- src/pyaudio.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pyaudio.py b/src/pyaudio.py index df56367..5802e71 100644 --- a/src/pyaudio.py +++ b/src/pyaudio.py @@ -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 From d786cc59a27416d27deaa4c9ce1ac0d235459a8b Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sat, 17 Oct 2015 12:35:48 -0700 Subject: [PATCH 04/32] Update examples for Python 3 compatibility. --- test/play_wave.py | 2 +- test/play_wave_macosx_channelmap.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/play_wave.py b/test/play_wave.py index 9c6c36f..19c036d 100644 --- a/test/play_wave.py +++ b/test/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_macosx_channelmap.py b/test/play_wave_macosx_channelmap.py index ea0816a..3fbdb8b 100644 --- a/test/play_wave_macosx_channelmap.py +++ b/test/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) From 3e6adb45880a64e01c67a2bc71e1c1737aeff31e Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Fri, 16 Oct 2015 23:02:21 -0700 Subject: [PATCH 05/32] Update source code style for consistency. --- README | 3 +- setup.py | 2 +- sphinx/conf.py | 2 +- src/_portaudiomodule.c | 1972 +++++++++++++++++----------------------- src/_portaudiomodule.h | 2 +- src/pyaudio.py | 2 +- 6 files changed, 824 insertions(+), 1159 deletions(-) diff --git a/README b/README index 661854b..7066f64 100644 --- a/README +++ b/README @@ -14,7 +14,7 @@ See INSTALL for 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 +36,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/setup.py b/setup.py index 6bcb06a..3fefdde 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ """ PyAudio v0.2.8: 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 diff --git a/sphinx/conf.py b/sphinx/conf.py index e240ad9..a8bce78 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py @@ -42,7 +42,7 @@ # 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 diff --git a/src/_portaudiomodule.c b/src/_portaudiomodule.c index e7ddee9..b5bad08 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,12 @@ #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 min(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a < _b ? _a : _b; \ + }) /************************************************************ * @@ -62,7 +64,6 @@ * ************************************************************/ - /************************************************************ * * I. Exportable Python Methods @@ -70,86 +71,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 +151,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 +536,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 +547,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 +563,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 +606,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 +637,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 +666,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 +676,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 +752,30 @@ typedef struct { } PyAudioCallbackContext; typedef struct { + // clang-format off PyObject_HEAD - PaStream *stream; + // 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 - streamObject->stream = NULL; + // 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,208 +793,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("(i,s)", - paBadStreamPtr, "Stream closed")); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } if ((!self->streamInfo)) { - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - paBadStreamPtr, - "No StreamInfo available")); + 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("(i,s)", - paBadStreamPtr, "Stream closed")); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - /* sanity check */ if ((!self->streamInfo)) { - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - paBadStreamPtr, - "No StreamInfo available")); + 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("(i,s)", - paBadStreamPtr, "Stream closed")); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - /* sanity check */ if ((!self->streamInfo)) { - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - paBadStreamPtr, - "No StreamInfo available")); + 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("(i,s)", - paBadStreamPtr, "Stream closed")); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } - /* sanity check */ if ((!self->streamInfo)) { - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - paBadStreamPtr, - "No StreamInfo available")); + 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 @@ -1174,20 +953,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()); } @@ -1196,9 +973,7 @@ 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; err = Pa_Initialize(); if (err != paNoError) { @@ -1209,8 +984,7 @@ pa_initialize(PyObject *self, PyObject *args) fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - err, Pa_GetErrorText(err))); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -1218,9 +992,7 @@ 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) { Pa_Terminate(); Py_INCREF(Py_None); return Py_None; @@ -1230,18 +1002,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); @@ -1249,26 +1019,23 @@ pa_get_host_api_count(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - count, Pa_GetErrorText(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); @@ -1276,27 +1043,25 @@ pa_get_default_host_api(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - index, Pa_GetErrorText(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); @@ -1304,85 +1069,73 @@ pa_host_api_type_id_to_host_api_index(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - index, Pa_GetErrorText(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("(i,s)", - devIndex, - Pa_GetErrorText(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("(i,s)", - paInvalidHostApi, - "Invalid host api info")); + 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); @@ -1390,28 +1143,25 @@ pa_get_device_count(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - count, Pa_GetErrorText(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); @@ -1419,28 +1169,25 @@ pa_get_default_input_device(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - index, Pa_GetErrorText(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); @@ -1448,51 +1195,43 @@ pa_get_default_output_device(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - index, Pa_GetErrorText(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("(i,s)", - paInvalidDevice, - "Invalid device info")); + 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(); @@ -1523,6 +1262,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, @@ -1530,6 +1270,7 @@ _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; @@ -1537,16 +1278,13 @@ _stream_callback_cfunction(const void *input, 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 @@ -1556,23 +1294,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"); @@ -1581,12 +1319,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,38 +1333,32 @@ _stream_callback_cfunction(const void *input, 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 + 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, 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; + } } - end: - +end: if (input) { // Decrement this at the end, after memcpy, in case the user // returns py_input_data back for playback. @@ -1642,9 +1373,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; @@ -1663,23 +1392,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 @@ -1693,57 +1420,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 @@ -1753,24 +1477,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 @@ -1778,7 +1498,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; @@ -1790,49 +1509,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("(i,s)", - paInvalidDevice, + Py_BuildValue("(i,s)", paInvalidDevice, "Invalid output device " - "(no default 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; @@ -1842,56 +1555,51 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) if (inputParameters->device < 0) { free(inputParameters); PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - paInvalidDevice, + Py_BuildValue("(i,s)", paInvalidDevice, "Invalid input device " - "(no default output 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; } 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); if (err != paNoError) { - #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); @@ -1899,17 +1607,14 @@ pa_open(PyObject *self, PyObject *args, PyObject *kwargs) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - err, Pa_GetErrorText(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("(i,s)", - paInternalError, + Py_BuildValue("(i,s)", paInternalError, "Could not get stream information")); return NULL; @@ -1922,20 +1627,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); @@ -1943,44 +1646,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; @@ -1990,20 +1689,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; @@ -2022,17 +1723,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; } } @@ -2041,31 +1740,28 @@ 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("(i,s)", - paBadStreamPtr, - "Stream closed")); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } stream = streamObject->stream; - if ( ((err = Pa_StartStream(stream)) != paNoError) && - (err != paStreamIsNotStopped)) { + if (((err = Pa_StartStream(stream)) != paNoError) && + (err != paStreamIsNotStopped)) { _cleanup_Stream_object(streamObject); #ifdef VERBOSE @@ -2075,8 +1771,7 @@ pa_start_stream(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - err, Pa_GetErrorText(err))); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -2084,19 +1779,17 @@ 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"); @@ -2105,11 +1798,13 @@ pa_stop_stream(PyObject *self, PyObject *args) stream = streamObject->stream; + // clang-format off Py_BEGIN_ALLOW_THREADS err = Pa_StopStream(stream); Py_END_ALLOW_THREADS + // clang-format on - if ((err != paNoError) && (err != paStreamIsStopped)) { + if ((err != paNoError) && (err != paStreamIsStopped)) { _cleanup_Stream_object(streamObject); #ifdef VERBOSE @@ -2119,8 +1814,7 @@ pa_stop_stream(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - err, Pa_GetErrorText(err))); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -2128,18 +1822,17 @@ 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"); @@ -2148,11 +1841,13 @@ pa_abort_stream(PyObject *self, PyObject *args) stream = streamObject->stream; + // clang-format off Py_BEGIN_ALLOW_THREADS err = Pa_AbortStream(stream); Py_END_ALLOW_THREADS + // clang-format on - if ((err != paNoError) && (err != paStreamIsStopped)) { + if ((err != paNoError) && (err != paStreamIsStopped)) { _cleanup_Stream_object(streamObject); #ifdef VERBOSE @@ -2162,8 +1857,7 @@ pa_abort_stream(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - err, Pa_GetErrorText(err))); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -2171,23 +1865,21 @@ 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("(i,s)", - paBadStreamPtr, "Stream closed")); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } @@ -2203,8 +1895,7 @@ pa_is_stream_stopped(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - err, Pa_GetErrorText(err))); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -2217,19 +1908,17 @@ 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"); @@ -2248,8 +1937,7 @@ pa_is_stream_active(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - err, Pa_GetErrorText(err))); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } @@ -2262,23 +1950,21 @@ 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("(i,s)", - paBadStreamPtr, "Stream closed")); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } @@ -2287,30 +1973,27 @@ pa_get_stream_time(PyObject *self, PyObject *args) if ((time = Pa_GetStreamTime(stream)) == 0) { _cleanup_Stream_object(streamObject); PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - paInternalError, "Internal Error")); + 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) { 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("(i,s)", - paBadStreamPtr, "Stream closed")); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } @@ -2318,14 +2001,11 @@ pa_get_stream_cpu_load(PyObject *self, PyObject *args) return PyFloat_FromDouble(Pa_GetStreamCpuLoad(stream)); } - /************************************************************* * 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; @@ -2336,41 +2016,44 @@ pa_write_stream(PyObject *self, PyObject *args) _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("(i,s)", - paBadStreamPtr, "Stream closed")); + 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); Py_END_ALLOW_THREADS + // clang-format on - if (err != paNoError) { + if (err != paNoError) { if (err == paOutputUnderflowed) { - if (should_throw_exception) - goto error; + if (should_throw_exception) { + goto error; + } } else goto error; } @@ -2378,8 +2061,7 @@ pa_write_stream(PyObject *self, PyObject *args) Py_INCREF(Py_None); return Py_None; - error: - /* cleanup */ +error: _cleanup_Stream_object(streamObject); #ifdef VERBOSE @@ -2389,15 +2071,11 @@ pa_write_stream(PyObject *self, PyObject *args) #endif PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i,s)", - err, - Pa_GetErrorText(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; @@ -2410,74 +2088,70 @@ pa_read_stream(PyObject *self, PyObject *args) PaStream *stream; PaStreamParameters *inputParameters; + // clang-format off if (!PyArg_ParseTuple(args, "O!i|i", - &_pyAudio_StreamType, - &stream_arg, - &total_frames, - &should_raise_exception)) + &_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("(i,s)", - paBadStreamPtr, "Stream closed")); + 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("(i,s)", - paInsufficientMemory, - "Out of memory")); + 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); Py_END_ALLOW_THREADS + // clang-format on - if (err != paNoError) { + if (err != paNoError) { if (err == paInputOverflowed) { if (should_raise_exception) { - goto error; + goto error; } } else { - goto error; + goto error; } } return rv; - error: - /* clean up */ +error: _cleanup_Stream_object(streamObject); - - /* free the string buffer */ Py_XDECREF(rv); - PyErr_SetObject(PyExc_IOError, - Py_BuildValue("(i, s)", - err, Pa_GetErrorText(err))); + Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); @@ -2488,23 +2162,21 @@ pa_read_stream(PyObject *self, PyObject *args) 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("(i,s)", - paBadStreamPtr, "Stream closed")); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } @@ -2513,23 +2185,21 @@ pa_get_stream_write_available(PyObject *self, PyObject *args) 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("(i,s)", - paBadStreamPtr, "Stream closed")); + Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } @@ -2538,7 +2208,6 @@ pa_get_stream_read_available(PyObject *self, PyObject *args) return PyLong_FromLong(frames); } - /************************************************************ * * IV. Python Module Init @@ -2552,17 +2221,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 @@ -2572,26 +2240,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 @@ -2606,9 +2278,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 */ @@ -2642,19 +2314,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); @@ -2663,7 +2332,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); @@ -2671,15 +2340,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); @@ -2695,27 +2364,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 5802e71..d79cbf2 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 From 0e5eb7cb9dbe0c51d4d3791f09d58f8b2210b14c Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sun, 18 Oct 2015 03:30:38 -0700 Subject: [PATCH 06/32] Use setuptools when possible. --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) mode change 100644 => 100755 setup.py diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 3fefdde..e281c03 --- a/setup.py +++ b/setup.py @@ -26,7 +26,10 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from distutils.core import setup, Extension +try: + from setuptools import setup, Extension +except ImportError: + from distutils.core import setup, Extension import sys import os From 5a4da7d870b0ea6d0c2e1c1ab52101ba1644caf9 Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sun, 18 Oct 2015 04:29:08 -0700 Subject: [PATCH 07/32] Update INSTALL instructions. --- INSTALL | 86 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 42 deletions(-) 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. From ab5e5b775e4050272387463add706a9038b88b1a Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sun, 18 Oct 2015 15:37:25 -0700 Subject: [PATCH 08/32] Update setup.py and cleanup. --- setup.py | 97 ++++++++++++++++++++++++-------------------------------- 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/setup.py b/setup.py index e281c03..f7cab57 100755 --- a/setup.py +++ b/setup.py @@ -26,28 +26,25 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. """ +import os +import platform +import sys try: from setuptools import setup, Extension except ImportError: from distutils.core import setup, Extension -import sys -import os __version__ = "0.2.8" -# Note: distutils will try to locate and link dynamically -# against portaudio. +# 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. +# If you would rather statically link in the portaudio library (e.g., +# typically on Microsoft Windows), run: # -# In any case, if you would rather statically link in libportaudio, -# 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 @@ -59,7 +56,6 @@ mac_sysroot_path = os.environ.get("SYSROOT_PATH", None) pyaudio_module_sources = ['src/_portaudiomodule.c'] - include_dirs = [] external_libraries = [] extra_compile_args = [] @@ -67,24 +63,24 @@ 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: + external_libraries = ['portaudio'] + extra_link_args = [] +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': @@ -92,45 +88,36 @@ '-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]) +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=[ + 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) + ]) From dce064b428aa3514a16835ae402138f54fb87471 Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sun, 18 Oct 2015 03:48:41 -0700 Subject: [PATCH 09/32] Version bump. --- CHANGELOG | 26 +++++++++++++++++++++++++- Makefile | 2 +- README | 2 +- setup.py | 4 ++-- sphinx/conf.py | 4 ++-- src/pyaudio.py | 2 +- 6 files changed, 32 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3eeb31e..34f6be0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,28 @@ +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 +121,3 @@ 2008-02-12 Justin Mazzola Paluska - Initial version of debian packaging. - diff --git a/Makefile b/Makefile index a6bd05c..cd10ed6 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ .PHONY: docs clean build -VERSION := 0.2.8 +VERSION := 0.2.9 PYTHON ?= python BUILD_ARGS ?= SPHINX ?= sphinx-build diff --git a/README b/README index 7066f64..340ac79 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ ====================================================================== -PyAudio v0.2.8: Python Bindings for PortAudio. +PyAudio v0.2.9: Python Bindings for PortAudio. ====================================================================== See: http://people.csail.mit.edu/hubert/pyaudio/ diff --git a/setup.py b/setup.py index f7cab57..cccebeb 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ """ -PyAudio v0.2.8: Python Bindings for PortAudio. +PyAudio v0.2.9: Python Bindings for PortAudio. Copyright (c) 2006 Hubert Pham @@ -34,7 +34,7 @@ except ImportError: from distutils.core import setup, Extension -__version__ = "0.2.8" +__version__ = "0.2.9" # distutils will try to locate and link dynamically against portaudio. # diff --git a/sphinx/conf.py b/sphinx/conf.py index a8bce78..09936a9 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py @@ -49,9 +49,9 @@ # built documents. # # The short X.Y version. -version = '0.2.8' +version = '0.2.9' # The full version, including alpha/beta/rc tags. -release = '0.2.8' +release = '0.2.9' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/pyaudio.py b/src/pyaudio.py index d79cbf2..0ed79bb 100644 --- a/src/pyaudio.py +++ b/src/pyaudio.py @@ -106,7 +106,7 @@ """ __author__ = "Hubert Pham" -__version__ = "0.2.8" +__version__ = "0.2.9" __docformat__ = "restructuredtext en" import sys From da8c238eeee29797c40bef1be81c5d6f7e020d3d Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Mon, 21 Sep 2015 23:33:06 -0700 Subject: [PATCH 10/32] Add unit tests. --- test/unit/error_tests.py | 111 +++++++++++++++++++++ test/unit/pyaudio_tests.py | 194 +++++++++++++++++++++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 test/unit/error_tests.py create mode 100644 test/unit/pyaudio_tests.py diff --git a/test/unit/error_tests.py b/test/unit/error_tests.py new file mode 100644 index 0000000..76bd987 --- /dev/null +++ b/test/unit/error_tests.py @@ -0,0 +1,111 @@ +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) + + 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) + + 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/test/unit/pyaudio_tests.py b/test/unit/pyaudio_tests.py new file mode 100644 index 0000000..6d23300 --- /dev/null +++ b/test/unit/pyaudio_tests.py @@ -0,0 +1,194 @@ +import random +import struct +import time +import unittest +import wave + +import pyaudio + +class PyAudioTests(unittest.TestCase): + def setUp(self): + self.p = pyaudio.PyAudio() + + def tearDown(self): + self.p.terminate() + + 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): + """ + While playing an audio file, record its playback and compare + with source audio file to verify audio playback and capture. + + IMPORTANT: this test requires an OS loopback sound device that + merges output and input audio. That is, there must a device + that acts as both an output and input, forwarding any output + data to the input and vice versa. On Mac OS X, one can use + Soundflower to create such a device. + """ + # Create _approximately_ 1s of noise + rate = 44100 # samples per second + width = 2 # bytes per sample + channels = 2 + seconds = 1 + + samples_per_chunk = 1024 + total_samples = rate * seconds + chunks = total_samples // samples_per_chunk + + audio_chunks = [ + b''.join([struct.pack('H', random.getrandbits(width * 8)) + + struct.pack('H', random.getrandbits(width * 8)) + for _ in range(samples_per_chunk)]) + for __ in range(chunks)] + + input_idx, output_idx = self._find_audio_loopback() + assert input_idx and output_idx, "No loopback device found" + + out_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + output=True, + frames_per_buffer=samples_per_chunk, + output_device_index=output_idx) + + in_stream = self.p.open( + format=self.p.get_format_from_width(width), + channels=channels, + rate=rate, + input=True, + frames_per_buffer=samples_per_chunk, + input_device_index=input_idx) + + captured = [] + for chunk in audio_chunks: + out_stream.write(chunk) + captured.append(in_stream.read(samples_per_chunk)) + # Capture a few more frames, since there is likely some lag. + for i in range(10): + captured.append(in_stream.read(samples_per_chunk)) + + self.assert_pcm16_nearly_equal( + self._trim_captured(b''.join(captured)), + b''.join(audio_chunks)) + + def test_input_output_callback(self): + """ + While playing an audio file, record its playback and compare + with source audio file to verify audio playback and capture. + This version uses PyAudio/PortAudio's callback approach. + + IMPORTANT: this test requires an OS loopback sound device that + merges output and input audio. That is, there must a device + that acts as both an output and input, forwarding any output + data to the input and vice versa. On Mac OS X, one can use + Soundflower to create such a device. + """ + # Create _approximately_ 1s of noise + rate = 44100 # samples per second + width = 2 # bytes per sample + channels = 2 + seconds = 1 + + samples_per_chunk = 1024 + total_samples = rate * seconds + chunks = total_samples // samples_per_chunk + + audio_chunks = [ + b''.join([struct.pack('H', random.getrandbits(width * 8)) + + struct.pack('H', random.getrandbits(width * 8)) + for _ in range(samples_per_chunk)]) + for __ in range(chunks)] + + input_idx, output_idx = self._find_audio_loopback() + assert input_idx and output_idx, "No loopback device found" + + 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=samples_per_chunk, + output_device_index=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=samples_per_chunk, + input_device_index=input_idx, + stream_callback=in_callback) + + in_stream.start_stream() + out_stream.start_stream() + time.sleep(seconds + 1) + in_stream.stop_stream() + out_stream.stop_stream() + + self.assert_pcm16_nearly_equal( + self._trim_captured(b''.join(captured)), + b''.join(audio_chunks)) + + def assert_pcm16_nearly_equal(self, wav1, wav2, tolerance=10): + self.assertEquals(len(wav1), len(wav2)) + nsamples = len(wav1) / 2 # 2 bytes per sample + wav1 = struct.unpack('%dh' % nsamples, wav1) + wav2 = struct.unpack('%dh' % nsamples, wav2) + max_diff = max([abs(w1 - w2) for w1, w2 in zip(wav1, wav2)]) + self.assertLessEqual(max_diff, tolerance) + + def _find_audio_loopback(self): + """Utility to find audio loopback device.""" + input_idx, output_idx = None, None + for device_idx in range(self.p.get_device_count()): + devinfo = self.p.get_device_info_by_index(device_idx) + if 'Soundflower (2ch)' in devinfo.get('name'): + if devinfo.get('maxOutputChannels', 0) > 0: + output_idx = device_idx + if devinfo.get('maxInputChannels', 0) > 0: + input_idx = device_idx + + if output_idx is not None and input_idx is not None: + break + + return input_idx, output_idx + + @staticmethod + def _trim_captured(waveform): + """ + Utility to trim silence from beginning and end of waveform. + """ + start_idx, end_idx = None, None + for idx, byte in enumerate(waveform): + if byte != b'\x00' and byte != 0: + start_idx = idx + break + for idx, byte in reversed(list(enumerate(waveform))): + if byte != b'\x00' and byte != 0: + end_idx = idx + break + + assert start_idx is not None + assert end_idx is not None + return waveform[start_idx:end_idx+1] From bff409df19630caaa1d53ec8de81374d8c3e7895 Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Tue, 22 Sep 2015 00:12:47 -0700 Subject: [PATCH 11/32] Rename test -> examples. --- MANIFEST.in | 3 ++- {test => examples}/error.py | 0 {test => examples}/play_wave.py | 0 {test => examples}/play_wave_callback.py | 0 {test => examples}/play_wave_macosx_channelmap.py | 0 {test => examples}/record.py | 0 {test => examples}/system_info.py | 0 {test => examples}/wire_callback.py | 0 {test => examples}/wire_full.py | 0 {test => examples}/wire_half.py | 0 {test/unit => tests}/error_tests.py | 0 {test/unit => tests}/pyaudio_tests.py | 0 12 files changed, 2 insertions(+), 1 deletion(-) rename {test => examples}/error.py (100%) rename {test => examples}/play_wave.py (100%) rename {test => examples}/play_wave_callback.py (100%) rename {test => examples}/play_wave_macosx_channelmap.py (100%) rename {test => examples}/record.py (100%) rename {test => examples}/system_info.py (100%) rename {test => examples}/wire_callback.py (100%) rename {test => examples}/wire_full.py (100%) rename {test => examples}/wire_half.py (100%) rename {test/unit => tests}/error_tests.py (100%) rename {test/unit => tests}/pyaudio_tests.py (100%) diff --git a/MANIFEST.in b/MANIFEST.in index a19b43a..a4a4ef0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include src/*.c src/*.h src/*.py include Makefile CHANGELOG INSTALL MANIFEST.in -recursive-include test *.py *.c +recursive-include examples *.py +recursive-include tests *.py graft docs graft sphinx 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 100% rename from test/play_wave.py rename to examples/play_wave.py 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 100% rename from test/play_wave_macosx_channelmap.py rename to examples/play_wave_macosx_channelmap.py 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 100% rename from test/wire_callback.py rename to examples/wire_callback.py 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/test/unit/error_tests.py b/tests/error_tests.py similarity index 100% rename from test/unit/error_tests.py rename to tests/error_tests.py diff --git a/test/unit/pyaudio_tests.py b/tests/pyaudio_tests.py similarity index 100% rename from test/unit/pyaudio_tests.py rename to tests/pyaudio_tests.py From 870016f6d1f037421c9db2fa74ea9ff42279f8ba Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Tue, 22 Sep 2015 01:40:50 -0700 Subject: [PATCH 12/32] Skip overflow tests on GNU/Linux ALSA. --- tests/error_tests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/error_tests.py b/tests/error_tests.py index 76bd987..6672139 100644 --- a/tests/error_tests.py +++ b/tests/error_tests.py @@ -1,3 +1,4 @@ +import sys import time import unittest @@ -88,6 +89,10 @@ def test_write_underflow_exception(self): 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) @@ -103,6 +108,10 @@ def test_read_overflow_exception(self): 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) From e59fa4a24e72570993e9c2d98b9e728ac25dff97 Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sat, 7 Jan 2017 22:23:42 -0800 Subject: [PATCH 13/32] Add loopback-based tests. --- tests/pyaudio_tests.py | 311 +++++++++++++++++++++++++++-------------- 1 file changed, 203 insertions(+), 108 deletions(-) diff --git a/tests/pyaudio_tests.py b/tests/pyaudio_tests.py index 6d23300..3372a48 100644 --- a/tests/pyaudio_tests.py +++ b/tests/pyaudio_tests.py @@ -1,18 +1,77 @@ -import random +# -*- 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 scipy.signal 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) @@ -21,93 +80,77 @@ def test_system_info(self): self.assertTrue(len(api_info.items()) > 0) def test_input_output_blocking(self): - """ - While playing an audio file, record its playback and compare - with source audio file to verify audio playback and capture. - - IMPORTANT: this test requires an OS loopback sound device that - merges output and input audio. That is, there must a device - that acts as both an output and input, forwarding any output - data to the input and vice versa. On Mac OS X, one can use - Soundflower to create such a device. - """ - # Create _approximately_ 1s of noise - rate = 44100 # samples per second + """Test blocking-based record and playback.""" + rate = 44100 # frames per second width = 2 # bytes per sample channels = 2 - seconds = 1 - - samples_per_chunk = 1024 - total_samples = rate * seconds - chunks = total_samples // samples_per_chunk - - audio_chunks = [ - b''.join([struct.pack('H', random.getrandbits(width * 8)) + - struct.pack('H', random.getrandbits(width * 8)) - for _ in range(samples_per_chunk)]) - for __ in range(chunks)] + # Blocking-mode might add some initial choppiness on some + # platforms/loopback devices, so set a longer duration. + duration = 3 # seconds + frames_per_chunk = 1024 - input_idx, output_idx = self._find_audio_loopback() - assert input_idx and output_idx, "No loopback device found" + 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=samples_per_chunk, - output_device_index=output_idx) - + 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=samples_per_chunk, - input_device_index=input_idx) + 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(samples_per_chunk)) - # Capture a few more frames, since there is likely some lag. - for i in range(10): - captured.append(in_stream.read(samples_per_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) - self.assert_pcm16_nearly_equal( - self._trim_captured(b''.join(captured)), - b''.join(audio_chunks)) + 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): - """ - While playing an audio file, record its playback and compare - with source audio file to verify audio playback and capture. - This version uses PyAudio/PortAudio's callback approach. - - IMPORTANT: this test requires an OS loopback sound device that - merges output and input audio. That is, there must a device - that acts as both an output and input, forwarding any output - data to the input and vice versa. On Mac OS X, one can use - Soundflower to create such a device. - """ - # Create _approximately_ 1s of noise - rate = 44100 # samples per second + """Test callback-based record and playback.""" + rate = 44100 # frames per second width = 2 # bytes per sample channels = 2 - seconds = 1 + duration = 1 # second + frames_per_chunk = 1024 - samples_per_chunk = 1024 - total_samples = rate * seconds - chunks = total_samples // samples_per_chunk - - audio_chunks = [ - b''.join([struct.pack('H', random.getrandbits(width * 8)) + - struct.pack('H', random.getrandbits(width * 8)) - for _ in range(samples_per_chunk)]) - for __ in range(chunks)] - - input_idx, output_idx = self._find_audio_loopback() - assert input_idx and output_idx, "No loopback device found" + 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): @@ -127,8 +170,8 @@ def in_callback(in_data, frame_count, time_info, status): channels=channels, rate=rate, output=True, - frames_per_buffer=samples_per_chunk, - output_device_index=output_idx, + frames_per_buffer=frames_per_chunk, + output_device_index=self.loopback_output_idx, stream_callback=out_callback) in_stream = self.p.open( @@ -136,59 +179,111 @@ def in_callback(in_data, frame_count, time_info, status): channels=channels, rate=rate, input=True, - frames_per_buffer=samples_per_chunk, - input_device_index=input_idx, + 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(seconds + 1) + time.sleep(duration + 1) in_stream.stop_stream() out_stream.stop_stream() - self.assert_pcm16_nearly_equal( - self._trim_captured(b''.join(captured)), - b''.join(audio_chunks)) + if DUMP_CAPTURE: + self.write_wav('test_callback.wav', b''.join(captured), + width, channels, rate) - def assert_pcm16_nearly_equal(self, wav1, wav2, tolerance=10): - self.assertEquals(len(wav1), len(wav2)) - nsamples = len(wav1) / 2 # 2 bytes per sample - wav1 = struct.unpack('%dh' % nsamples, wav1) - wav2 = struct.unpack('%dh' % nsamples, wav2) - max_diff = max([abs(w1 - w2) for w1, w2 in zip(wav1, wav2)]) - self.assertLessEqual(max_diff, tolerance) + captured_signal = self.pcm16_to_numpy(b''.join(captured)) + captured_left_channel = captured_signal[::2] + captured_right_channel = captured_signal[1::2] - def _find_audio_loopback(self): - """Utility to find audio loopback device.""" - input_idx, output_idx = None, None - for device_idx in range(self.p.get_device_count()): - devinfo = self.p.get_device_info_by_index(device_idx) - if 'Soundflower (2ch)' in devinfo.get('name'): - if devinfo.get('maxOutputChannels', 0) > 0: - output_idx = device_idx - if devinfo.get('maxInputChannels', 0) > 0: - input_idx = device_idx + 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)) - if output_idx is not None and input_idx is not None: - break - - return input_idx, output_idx + @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 _trim_captured(waveform): + 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. """ - Utility to trim silence from beginning and end of waveform. - """ - start_idx, end_idx = None, None - for idx, byte in enumerate(waveform): - if byte != b'\x00' and byte != 0: - start_idx = idx - break - for idx, byte in reversed(list(enumerate(waveform))): - if byte != b'\x00' and byte != 0: - end_idx = idx - break + 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) - assert start_idx is not None - assert end_idx is not None - return waveform[start_idx:end_idx+1] + # 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( + scipy.signal.correlate(cap_fft, ref_fft), + bins=10) + self.assertLess(sum(freq_corr_hist[2:])/sum(freq_corr_hist), 1e-2) From 7a610802700e8bbe13fb81682d65ea6c7a3baf2a Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sun, 8 Jan 2017 12:18:07 -0800 Subject: [PATCH 14/32] Add duration for wire callback example. --- examples/wire_callback.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/wire_callback.py b/examples/wire_callback.py index f214bf9..10d1ac4 100644 --- a/examples/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() From 884cedae7f2dca14b30ad3606cfa98653d57ae5e Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sun, 8 Jan 2017 19:12:51 -0800 Subject: [PATCH 15/32] Release GIL when calling Pa_{Open,Start}Stream. On macOS/OSX, CoreAudio may require a device-level lock for certain operations, e.g., opening a stream or delivering audio data to a callback. If the lock acquisition order between the device lock and the Python GIL is inconsistent, deadlock might occur. Michael Graczyk identified one such instance, where: - an input stream, running in thread A, acquires the device lock, followed by the GIL, in order to call the user's Python input callback handler; - that callback makes a call that releases the GIL (e.g., I/O) - thread B acquires the GIL and opens a PyAudio stream, eventually invoking Pa_OpenStream. Note that prior to this patch, calling Pa_OpenStream did _not_ release the GIL. - While holding the GIL, Pa_OpenStream attempts to acquire the device lock. - Meanwhile, thread A (still holding the device lock) attempts to reacquire the GIL (e.g., once the I/O call is complete). This commit: (a) updates the implementation to release the GIL before calling Pa_OpenStream and Pa_StartStream. Hopefully this change will bring the code closer to the discipline of always acquiring the device lock before the GIL. Thanks Michael for the patch! (b) adds a unit test to reproduce the problem (on CoreAudio systems). Many thanks again to Michael Graczyk for identifying the issue and contributing the fix! --- src/_portaudiomodule.c | 20 ++++++++++++---- tests/pyaudio_tests.py | 54 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/_portaudiomodule.c b/src/_portaudiomodule.c index b5bad08..ea84141 100644 --- a/src/_portaudiomodule.c +++ b/src/_portaudiomodule.c @@ -1583,6 +1583,8 @@ static PyObject *pa_open(PyObject *self, PyObject *args, PyObject *kwargs) { 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 */ @@ -1598,6 +1600,8 @@ static PyObject *pa_open(PyObject *self, PyObject *args, PyObject *kwargs) { (stream_callback) ? (_stream_callback_cfunction) : (NULL), /* callback userData, if applicable */ context); + Py_END_ALLOW_THREADS + // clang-format on if (err != paNoError) { #ifdef VERBOSE @@ -1760,7 +1764,13 @@ static PyObject *pa_start_stream(PyObject *self, PyObject *args) { stream = streamObject->stream; - if (((err = Pa_StartStream(stream)) != paNoError) && + // clang-format off + Py_BEGIN_ALLOW_THREADS + err = Pa_StartStream(stream); + Py_END_ALLOW_THREADS + // clang-format on + + if ((err != paNoError) && (err != paStreamIsNotStopped)) { _cleanup_Stream_object(streamObject); @@ -1802,9 +1812,9 @@ static PyObject *pa_stop_stream(PyObject *self, PyObject *args) { Py_BEGIN_ALLOW_THREADS err = Pa_StopStream(stream); Py_END_ALLOW_THREADS - // clang-format on + // clang-format on - if ((err != paNoError) && (err != paStreamIsStopped)) { + if ((err != paNoError) && (err != paStreamIsStopped)) { _cleanup_Stream_object(streamObject); #ifdef VERBOSE @@ -1845,9 +1855,9 @@ static PyObject *pa_abort_stream(PyObject *self, PyObject *args) { Py_BEGIN_ALLOW_THREADS err = Pa_AbortStream(stream); Py_END_ALLOW_THREADS - // clang-format on + // clang-format on - if ((err != paNoError) && (err != paStreamIsStopped)) { + if ((err != paNoError) && (err != paStreamIsStopped)) { _cleanup_Stream_object(streamObject); #ifdef VERBOSE diff --git a/tests/pyaudio_tests.py b/tests/pyaudio_tests.py index 3372a48..e734728 100644 --- a/tests/pyaudio_tests.py +++ b/tests/pyaudio_tests.py @@ -208,6 +208,60 @@ def in_callback(in_data, frame_count, time_info, status): test_signal, len(freqs)) + def test_device_lock_gil_order(self): + """Ensure lock acquisition order is consistent: device lock then 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(3) + 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, + 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() + @staticmethod def create_reference_signal(freqs, sampling_rate, width, duration): """Return reference signal with several sinuoids with frequencies From c9bc0193b0bb8325f0c35cbdbad9107dd83bb41b Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Mon, 9 Jan 2017 20:06:04 -0800 Subject: [PATCH 16/32] Add tests to detect potential sources of deadlock. --- src/_portaudiomodule.c | 4 +- tests/pyaudio_tests.py | 304 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 304 insertions(+), 4 deletions(-) diff --git a/src/_portaudiomodule.c b/src/_portaudiomodule.c index ea84141..56cdd8e 100644 --- a/src/_portaudiomodule.c +++ b/src/_portaudiomodule.c @@ -771,8 +771,8 @@ static void _cleanup_Stream_object(_pyAudio_Stream *streamObject) { Py_BEGIN_ALLOW_THREADS Pa_CloseStream(streamObject->stream); Py_END_ALLOW_THREADS - // clang-format on - streamObject->stream = NULL; + // clang-format on + streamObject->stream = NULL; } if (streamObject->streamInfo) streamObject->streamInfo = NULL; diff --git a/tests/pyaudio_tests.py b/tests/pyaudio_tests.py index e734728..579174b 100644 --- a/tests/pyaudio_tests.py +++ b/tests/pyaudio_tests.py @@ -209,7 +209,7 @@ def in_callback(in_data, frame_count, time_info, status): len(freqs)) def test_device_lock_gil_order(self): - """Ensure lock acquisition order is consistent: device lock then GIL.""" + """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 @@ -224,7 +224,7 @@ def out_callback(_, frame_count, time_info, status): def in_callback(in_data, frame_count, time_info, status): # Release the GIL for a bit - time.sleep(3) + time.sleep(2) return (None, pyaudio.paComplete) in_stream = self.p.open( @@ -232,6 +232,7 @@ def in_callback(in_data, frame_count, time_info, status): 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) @@ -262,6 +263,305 @@ def in_callback(in_data, frame_count, time_info, status): 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 From 439327bcd7c14bd453cab327903742bef8ec9cef Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Mon, 9 Jan 2017 20:44:09 -0800 Subject: [PATCH 17/32] Minor fix on int type to fix compiler warnings. Also trivial formatting fixes. --- src/_portaudiomodule.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/_portaudiomodule.c b/src/_portaudiomodule.c index 56cdd8e..1e1ff5d 100644 --- a/src/_portaudiomodule.c +++ b/src/_portaudiomodule.c @@ -754,8 +754,8 @@ typedef struct { typedef struct { // clang-format off PyObject_HEAD - // clang-format on - PaStream *stream; + // clang-format on + PaStream *stream; PaStreamParameters *inputParameters; PaStreamParameters *outputParameters; PaStreamInfo *streamInfo; @@ -1274,7 +1274,7 @@ int _stream_callback_cfunction(const void *input, void *output, 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) { @@ -2057,9 +2057,9 @@ static PyObject *pa_write_stream(PyObject *self, PyObject *args) { Py_BEGIN_ALLOW_THREADS err = Pa_WriteStream(stream, data, total_frames); Py_END_ALLOW_THREADS - // clang-format on + // clang-format on - if (err != paNoError) { + if (err != paNoError) { if (err == paOutputUnderflowed) { if (should_throw_exception) { goto error; @@ -2143,9 +2143,9 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) { Py_BEGIN_ALLOW_THREADS err = Pa_ReadStream(stream, sampleBlock, total_frames); Py_END_ALLOW_THREADS - // clang-format on + // clang-format on - if (err != paNoError) { + if (err != paNoError) { if (err == paInputOverflowed) { if (should_raise_exception) { goto error; From 47a80684e14059c1d22a569a972ef568b7ac34f7 Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Mon, 9 Jan 2017 20:55:43 -0800 Subject: [PATCH 18/32] Release the GIL before PortAudio stream calls. This commit adds logic to release the GIL for these PortAudio routines: Pa_IsStreamStopped Pa_IsStreamActive Pa_GetStreamTime Pa_GetStreamCpuLoad Pa_GetStreamWriteAvailable Pa_GetStreamReadAvailable Pa_Initialize Pa_Terminate --- src/_portaudiomodule.c | 97 ++++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/src/_portaudiomodule.c b/src/_portaudiomodule.c index 1e1ff5d..73ca0e0 100644 --- a/src/_portaudiomodule.c +++ b/src/_portaudiomodule.c @@ -975,14 +975,26 @@ static PyObject *pa_get_version_text(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("(i,s)", err, Pa_GetErrorText(err))); return NULL; @@ -993,7 +1005,12 @@ static PyObject *pa_initialize(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; } @@ -1748,7 +1765,6 @@ 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)) { return NULL; @@ -1762,11 +1778,9 @@ static PyObject *pa_start_stream(PyObject *self, PyObject *args) { return NULL; } - stream = streamObject->stream; - // clang-format off Py_BEGIN_ALLOW_THREADS - err = Pa_StartStream(stream); + err = Pa_StartStream(streamObject->stream); Py_END_ALLOW_THREADS // clang-format on @@ -1793,7 +1807,6 @@ 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)) { return NULL; @@ -1806,11 +1819,9 @@ static PyObject *pa_stop_stream(PyObject *self, PyObject *args) { 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 @@ -1836,7 +1847,6 @@ 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)) { return NULL; @@ -1849,11 +1859,9 @@ static PyObject *pa_abort_stream(PyObject *self, PyObject *args) { 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 @@ -1879,7 +1887,6 @@ 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)) { return NULL; @@ -1893,9 +1900,13 @@ static PyObject *pa_is_stream_stopped(PyObject *self, PyObject *args) { 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 @@ -1922,7 +1933,6 @@ 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)) { return NULL; @@ -1935,9 +1945,13 @@ static PyObject *pa_is_stream_active(PyObject *self, PyObject *args) { 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 @@ -1964,7 +1978,6 @@ 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)) { return NULL; @@ -1978,9 +1991,13 @@ static PyObject *pa_get_stream_time(PyObject *self, PyObject *args) { 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("(i,s)", paInternalError, "Internal Error")); @@ -1991,9 +2008,9 @@ static PyObject *pa_get_stream_time(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)) { return NULL; @@ -2007,8 +2024,13 @@ static PyObject *pa_get_stream_cpu_load(PyObject *self, PyObject *args) { 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); } /************************************************************* @@ -2024,7 +2046,6 @@ static PyObject *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", @@ -2051,11 +2072,9 @@ static PyObject *pa_write_stream(PyObject *self, PyObject *args) { 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 @@ -2095,7 +2114,6 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) { PyObject *stream_arg; _pyAudio_Stream *streamObject; - PaStream *stream; PaStreamParameters *inputParameters; // clang-format off @@ -2121,7 +2139,6 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) { return NULL; } - stream = streamObject->stream; inputParameters = streamObject->inputParameters; num_bytes = (total_frames) * (inputParameters->channelCount) * (Pa_GetSampleSize(inputParameters->sampleFormat)); @@ -2141,7 +2158,7 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) { // 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 @@ -2176,7 +2193,6 @@ 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)) { return NULL; @@ -2190,8 +2206,12 @@ static PyObject *pa_get_stream_write_available(PyObject *self, PyObject *args) { 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); } @@ -2199,7 +2219,6 @@ 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)) { return NULL; @@ -2213,8 +2232,12 @@ static PyObject *pa_get_stream_read_available(PyObject *self, PyObject *args) { 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); } From a0f6d13628904441cef3c1a9190f3a8523e43a18 Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Mon, 9 Jan 2017 22:14:21 -0800 Subject: [PATCH 19/32] Tests: remove dependence on scipy. --- tests/pyaudio_tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/pyaudio_tests.py b/tests/pyaudio_tests.py index 579174b..1655a63 100644 --- a/tests/pyaudio_tests.py +++ b/tests/pyaudio_tests.py @@ -20,7 +20,6 @@ import sys import numpy -import scipy.signal import pyaudio @@ -638,6 +637,6 @@ def assert_pcm16_spectrum_nearly_equal(self, sampling_rate, cap, ref, # peak when the spectra overlap and mostly 0s elsewhere. Verify that # using a histogram of the cross-correlation: freq_corr_hist, _ = numpy.histogram( - scipy.signal.correlate(cap_fft, ref_fft), + numpy.correlate(cap_fft, ref_fft, mode='full'), bins=10) self.assertLess(sum(freq_corr_hist[2:])/sum(freq_corr_hist), 1e-2) From fe528b20506bd516589cea2ad6a22f6231877a3d Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Tue, 10 Jan 2017 20:05:59 -0800 Subject: [PATCH 20/32] Update references to examples and tests. --- Makefile | 5 +++-- sphinx/examples.rst | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index cd10ed6..2ddac4a 100644 --- a/Makefile +++ b/Makefile @@ -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: docs $(SRCFILES) $(EXAMPLES) $(TESTS) MANIFEST.in @$(PYTHON) setup.py sdist 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 From 833ebc0b1453561509cf21918c11d2aef3f682ba Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Wed, 11 Jan 2017 20:03:43 -0800 Subject: [PATCH 21/32] Version bump. --- CHANGELOG | 10 ++++++++++ Makefile | 2 +- README | 2 +- setup.py | 4 ++-- sphinx/conf.py | 4 ++-- src/pyaudio.py | 2 +- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 34f6be0..9c88c4a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +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 diff --git a/Makefile b/Makefile index 2ddac4a..0b048e8 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ .PHONY: docs clean build -VERSION := 0.2.9 +VERSION := 0.2.10 PYTHON ?= python BUILD_ARGS ?= SPHINX ?= sphinx-build diff --git a/README b/README index 340ac79..fa98958 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ ====================================================================== -PyAudio v0.2.9: Python Bindings for PortAudio. +PyAudio v0.2.10: Python Bindings for PortAudio. ====================================================================== See: http://people.csail.mit.edu/hubert/pyaudio/ diff --git a/setup.py b/setup.py index cccebeb..dc75589 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ """ -PyAudio v0.2.9: Python Bindings for PortAudio. +PyAudio v0.2.10: Python Bindings for PortAudio. Copyright (c) 2006 Hubert Pham @@ -34,7 +34,7 @@ except ImportError: from distutils.core import setup, Extension -__version__ = "0.2.9" +__version__ = "0.2.10" # distutils will try to locate and link dynamically against portaudio. # diff --git a/sphinx/conf.py b/sphinx/conf.py index 09936a9..b634e70 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py @@ -49,9 +49,9 @@ # built documents. # # The short X.Y version. -version = '0.2.9' +version = '0.2.10' # The full version, including alpha/beta/rc tags. -release = '0.2.9' +release = '0.2.10' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/pyaudio.py b/src/pyaudio.py index 0ed79bb..c206755 100644 --- a/src/pyaudio.py +++ b/src/pyaudio.py @@ -106,7 +106,7 @@ """ __author__ = "Hubert Pham" -__version__ = "0.2.9" +__version__ = "0.2.10" __docformat__ = "restructuredtext en" import sys From 2e696d98c000effcbd8e3927ad1e046a595bb28a Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sun, 26 Feb 2017 19:58:48 -0800 Subject: [PATCH 22/32] Fix use-after-free error. Many thanks to Blaise Potard for spotting the issue and submitting a patch! --- src/_portaudiomodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_portaudiomodule.c b/src/_portaudiomodule.c index 73ca0e0..a5bfd60 100644 --- a/src/_portaudiomodule.c +++ b/src/_portaudiomodule.c @@ -1348,8 +1348,6 @@ int _stream_callback_cfunction(const void *input, void *output, goto end; } - Py_DECREF(py_result); - if ((return_val != paComplete) && (return_val != paAbort) && (return_val != paContinue)) { PyErr_SetString(PyExc_ValueError, @@ -1358,6 +1356,7 @@ int _stream_callback_cfunction(const void *input, void *output, PyErr_Print(); // Quit the callback loop + Py_DECREF(py_result); return_val = paAbort; goto end; } @@ -1374,6 +1373,7 @@ int _stream_callback_cfunction(const void *input, void *output, return_val = paComplete; } } + Py_DECREF(py_result); end: if (input) { From 281a8f32e76486df38d8b32b3b33f39478f48762 Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Tue, 28 Feb 2017 21:20:36 -0800 Subject: [PATCH 23/32] Fix get_output_latency docstring. --- src/pyaudio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyaudio.py b/src/pyaudio.py index c206755..52c0e1c 100644 --- a/src/pyaudio.py +++ b/src/pyaudio.py @@ -471,7 +471,7 @@ def get_input_latency(self): def get_output_latency(self): """ - Return the input latency. + Return the output latency. :rtype: float """ From 653fdfeb873f8c64e600de8f404917b537c10c56 Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sat, 18 Mar 2017 10:59:11 -0700 Subject: [PATCH 24/32] Remove generated files from source distribution. --- MANIFEST.in | 1 - Makefile | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index a4a4ef0..8214bd0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,5 +2,4 @@ include src/*.c src/*.h src/*.py include Makefile CHANGELOG INSTALL MANIFEST.in recursive-include examples *.py recursive-include tests *.py -graft docs graft sphinx diff --git a/Makefile b/Makefile index 0b048e8..83fc338 100644 --- a/Makefile +++ b/Makefile @@ -44,5 +44,5 @@ docs: build ###################################################################### # Source Tarball ###################################################################### -tarball: docs $(SRCFILES) $(EXAMPLES) $(TESTS) MANIFEST.in +tarball: $(SRCFILES) $(EXAMPLES) $(TESTS) MANIFEST.in @$(PYTHON) setup.py sdist From 7090e25bcba41413bd7ce89aa73bc0efb1ae1ca1 Mon Sep 17 00:00:00 2001 From: Hubert Pham Date: Sat, 18 Mar 2017 16:32:59 -0700 Subject: [PATCH 25/32] Version bump. --- CHANGELOG | 12 ++++++++++++ Makefile | 2 +- README | 2 +- setup.py | 4 ++-- sphinx/conf.py | 4 ++-- src/pyaudio.py | 2 +- 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9c88c4a..56f0d5a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,15 @@ +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 diff --git a/Makefile b/Makefile index 83fc338..c7c14a0 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ .PHONY: docs clean build -VERSION := 0.2.10 +VERSION := 0.2.11 PYTHON ?= python BUILD_ARGS ?= SPHINX ?= sphinx-build diff --git a/README b/README index fa98958..2d3ac21 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ ====================================================================== -PyAudio v0.2.10: Python Bindings for PortAudio. +PyAudio v0.2.11: Python Bindings for PortAudio. ====================================================================== See: http://people.csail.mit.edu/hubert/pyaudio/ diff --git a/setup.py b/setup.py index dc75589..01cdc7b 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ """ -PyAudio v0.2.10: Python Bindings for PortAudio. +PyAudio v0.2.11: Python Bindings for PortAudio. Copyright (c) 2006 Hubert Pham @@ -34,7 +34,7 @@ except ImportError: from distutils.core import setup, Extension -__version__ = "0.2.10" +__version__ = "0.2.11" # distutils will try to locate and link dynamically against portaudio. # diff --git a/sphinx/conf.py b/sphinx/conf.py index b634e70..240a54d 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py @@ -49,9 +49,9 @@ # built documents. # # The short X.Y version. -version = '0.2.10' +version = '0.2.11' # The full version, including alpha/beta/rc tags. -release = '0.2.10' +release = '0.2.11' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/src/pyaudio.py b/src/pyaudio.py index 52c0e1c..d4c3f2d 100644 --- a/src/pyaudio.py +++ b/src/pyaudio.py @@ -106,7 +106,7 @@ """ __author__ = "Hubert Pham" -__version__ = "0.2.10" +__version__ = "0.2.11" __docformat__ = "restructuredtext en" import sys From b37c601f488ef1ab3b85f0747ad5395c779fc87c Mon Sep 17 00:00:00 2001 From: evandrocoan Date: Sat, 22 Feb 2020 21:40:57 -0300 Subject: [PATCH 26/32] Fixed min() macro not compilling with MSVC compiler --- src/_portaudiomodule.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/_portaudiomodule.c b/src/_portaudiomodule.c index a5bfd60..3650cb2 100644 --- a/src/_portaudiomodule.c +++ b/src/_portaudiomodule.c @@ -36,12 +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)) /************************************************************ * @@ -1364,7 +1359,7 @@ int _stream_callback_cfunction(const void *input, void *output, // 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)); + 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)) { From e6ea652e81cb64c1debc48e1a248800ff5ca877c Mon Sep 17 00:00:00 2001 From: evandrocoan Date: Sun, 23 Feb 2020 15:56:40 -0300 Subject: [PATCH 27/32] Fixed installation for Visual Studio 2017 on setup.py --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 01cdc7b..e23d128 100755 --- a/setup.py +++ b/setup.py @@ -75,7 +75,12 @@ if not STATIC_LINKING: external_libraries = ['portaudio'] - extra_link_args = [] + + # 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 sys.platform.startswith('win32'): + extra_link_args = ['/VERBOSE:LIB', '/NODEFAULTLIB:libcmt.lib', '/DEFAULTLIB:advapi32.lib'] + else: include_dirs = [os.path.join(portaudio_path, 'include/')] extra_link_args = [ From 0b9d343a05429be5602bc57c85cfce9f3222d6e7 Mon Sep 17 00:00:00 2001 From: evandrocoan Date: Sun, 23 Feb 2020 17:09:56 -0300 Subject: [PATCH 28/32] Fixed usage of the 32 postfix on sys.platform because at Python 3.8 and forward, postfix wont be used anymore. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e23d128..1789ebd 100755 --- a/setup.py +++ b/setup.py @@ -78,7 +78,7 @@ # 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 sys.platform.startswith('win32'): + if sys.platform.startswith('win'): extra_link_args = ['/VERBOSE:LIB', '/NODEFAULTLIB:libcmt.lib', '/DEFAULTLIB:advapi32.lib'] else: From d8f3c93000a40b37c431f23107ac8e53bdfdcff8 Mon Sep 17 00:00:00 2001 From: evandrocoan Date: Mon, 24 Feb 2020 17:14:32 -0300 Subject: [PATCH 29/32] Fixed missing declaration of extra_link_args on setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 1789ebd..0dce533 100755 --- a/setup.py +++ b/setup.py @@ -74,6 +74,7 @@ defines.append(('MS_WIN64', '1')) if not STATIC_LINKING: + extra_link_args = [] external_libraries = ['portaudio'] # https://stackoverflow.com/questions/22954119/linker-error-while-linking-some-windows-apis From 0269349ad8a5025d20429369f1c7b18c9dee0e0b Mon Sep 17 00:00:00 2001 From: evandrocoan Date: Mon, 24 Feb 2020 17:14:57 -0300 Subject: [PATCH 30/32] Created a PyAudioAndPortaudio.sublime-project --- .gitignore | 1 + PyAudioAndPortaudio.sublime-project | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 PyAudioAndPortaudio.sublime-project 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/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": "." + } + ] +} From 340f30f8ebd3cc93903f4fcc59709275836990e7 Mon Sep 17 00:00:00 2001 From: evandrocoan Date: Tue, 25 Feb 2020 14:38:55 -0300 Subject: [PATCH 31/32] Added build Visual Studio instructions to README --- README | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README b/README index 2d3ac21..a0b4ca3 100644 --- a/README +++ b/README @@ -8,7 +8,18 @@ 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. ====================================================================== From 8609fc6097e73ec9802ffbdeb833029168c94fed Mon Sep 17 00:00:00 2001 From: evandrocoan Date: Tue, 25 Feb 2020 19:48:18 -0300 Subject: [PATCH 32/32] Fixed setup.py using MSVC flags when not using a MSVC compiler --- setup.py | 60 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/setup.py b/setup.py index 0dce533..e23d085 100755 --- a/setup.py +++ b/setup.py @@ -29,10 +29,14 @@ import os import platform import sys + try: from setuptools import setup, Extension + from setuptools.command.build_ext import build_ext + except ImportError: from distutils.core import setup, Extension + from distutils.command.build_ext import build_ext __version__ = "0.2.11" @@ -77,11 +81,6 @@ extra_link_args = [] external_libraries = ['portaudio'] - # 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 sys.platform.startswith('win'): - extra_link_args = ['/VERBOSE:LIB', '/NODEFAULTLIB:libcmt.lib', '/DEFAULTLIB:advapi32.lib'] - else: include_dirs = [os.path.join(portaudio_path, 'include/')] extra_link_args = [ @@ -109,6 +108,45 @@ # JACK, since that's common today. extra_link_args += ['-lasound', '-ljack'] + +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", @@ -116,14 +154,8 @@ description='PortAudio Python Bindings', long_description=__doc__.lstrip(), scripts=scripts, + cmdclass=cmdclass, py_modules=['pyaudio'], package_dir={'': 'src'}, - ext_modules=[ - 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) - ]) + ext_modules=[myextension] + )