From ba984d90fa4ace0c9be498276c82377ae6eb2429 Mon Sep 17 00:00:00 2001 From: Stephen Sinclair Date: Mon, 13 Jan 2020 17:12:54 +0100 Subject: [PATCH] PyRtAudio: Update to Python 3 Fixes #199. --- contrib/python/pyrtaudio/PyRtAudioTest.py | 141 +++++++++++---------- contrib/python/pyrtaudio/rtaudiomodule.cpp | 117 +++++++++++++++-- contrib/python/pyrtaudio/setup.py | 1 - 3 files changed, 180 insertions(+), 79 deletions(-) diff --git a/contrib/python/pyrtaudio/PyRtAudioTest.py b/contrib/python/pyrtaudio/PyRtAudioTest.py index 1966ce5..fcf85cb 100644 --- a/contrib/python/pyrtaudio/PyRtAudioTest.py +++ b/contrib/python/pyrtaudio/PyRtAudioTest.py @@ -1,70 +1,71 @@ - -import rtaudio as rt - -from math import cos - -import struct - - -class audio_generator: - def __init__(self): - self.idx = -1 - self.freq = 440. - def __call__(self): - self.idx += 1 - if self.idx%48000 == 0: - self.freq *= 2**(1/12.) - return 0.5*cos(2.*3.1416*self.freq*self.idx/48000.) - - -class callback: - def __init__(self, gen): - self.gen = gen - self.i = 0 - def __call__(self,playback, capture): - [struct.pack_into("f", playback, 4*o, self.gen()) for o in xrange(256)] - self.i = self.i + 256 - if self.i > 48000*10: - print '.' - return 1 - -dac = rt.RtAudio() - -n = dac.getDeviceCount() -print 'Number of devices available: ', n - -for i in range(n): - try: - print dac.getDeviceInfo(i) - except rt.RtError as e: - print e - - -print 'Default output device: ', dac.getDefaultOutputDevice() -print 'Default input device: ', dac.getDefaultInputDevice() - -print 'is stream open: ', dac.isStreamOpen() -print 'is stream running: ', dac.isStreamRunning() - -oParams = {'deviceId': 1, 'nChannels': 1, 'firstChannel': 0} -iParams = {'deviceId': 1, 'nChannels': 1, 'firstChannel': 0} - -try: - dac.openStream(oParams,oParams,48000,256,callback(audio_generator()) ) -except rt.RtError as e: - print e -else: - dac.startStream() - - import time - print 'latency: ', dac.getStreamLatency() - - while (dac.isStreamRunning()): - time.sleep(0.1) - - print dac.getStreamTime() - - dac.stopStream() - dac.abortStream() - dac.closeStream() - + +from __future__ import print_function +import threading +import rtaudio as rt + +from math import cos + +import struct + + +class audio_generator: + def __init__(self): + self.idx = -1 + self.freq = 440. + def __call__(self): + self.idx += 1 + if self.idx%48000 == 0: + self.freq *= 2**(1/12.) + return 0.5*cos(2.*3.1416*self.freq*self.idx/48000.) + + +class callback: + def __init__(self, gen): + self.gen = gen + self.i = 0 + def __call__(self,playback, capture): + [struct.pack_into("f", playback, 4*o, self.gen()) for o in range(256)] + self.i = self.i + 256 + if self.i > 48000*10: + print('.') + return 1 + +dac = rt.RtAudio() + +n = dac.getDeviceCount() +print('Number of devices available: ', n) + +for i in range(n): + try: + print(dac.getDeviceInfo(i)) + except rt.RtError as e: + print(e) + + +print('Default output device: ', dac.getDefaultOutputDevice()) +print('Default input device: ', dac.getDefaultInputDevice()) + +print('is stream open: ', dac.isStreamOpen()) +print('is stream running: ', dac.isStreamRunning()) + +oParams = {'deviceId': 0, 'nChannels': 1, 'firstChannel': 0} +iParams = {'deviceId': 0, 'nChannels': 1, 'firstChannel': 0} + +try: + dac.openStream(oParams,oParams,48000,256,callback(audio_generator()) ) +except rt.RtError as e: + print(e) +else: + dac.startStream() + + import time + print('latency: ', dac.getStreamLatency()) + + while (dac.isStreamRunning()): + time.sleep(0.1) + + print(dac.getStreamTime()) + + dac.stopStream() + dac.abortStream() + dac.closeStream() diff --git a/contrib/python/pyrtaudio/rtaudiomodule.cpp b/contrib/python/pyrtaudio/rtaudiomodule.cpp index 93d3c73..bc6f4b7 100644 --- a/contrib/python/pyrtaudio/rtaudiomodule.cpp +++ b/contrib/python/pyrtaudio/rtaudiomodule.cpp @@ -70,8 +70,13 @@ extern "C" { if (py_callback_func) { PyGILState_STATE gstate = PyGILState_Ensure(); +#if PY_MAJOR_VERSION >= 3 + PyObject* iBuffer = PyMemoryView_FromMemory((char*)in, sizeof(float) * self->inputChannels * nBufferFrames, PyBUF_READ); + PyObject* oBuffer = PyMemoryView_FromMemory((char*)out, sizeof(float) * nBufferFrames, PyBUF_WRITE); +#else PyObject* iBuffer = PyBuffer_FromMemory(in, sizeof(float) * self->inputChannels * nBufferFrames); PyObject* oBuffer = PyBuffer_FromReadWriteMemory(out, sizeof(float) * nBufferFrames); +#endif PyObject *arglist = Py_BuildValue("(O,O)", oBuffer, iBuffer); if (arglist == NULL) { @@ -87,9 +92,17 @@ extern "C" { if (PyErr_Occurred() != NULL) { PyErr_Print(); } - else if PyInt_Check(result) { +#if PY_MAJOR_VERSION >= 3 + else if (result == NULL) + retval = 0; + else if (PyLong_Check(result)) { + retval = PyLong_AsLong(result); + } +#else + else if (PyInt_Check(result)) { retval = PyInt_AsLong(result); } +#endif Py_DECREF(arglist); Py_DECREF(oBuffer); @@ -115,7 +128,7 @@ extern "C" { delete self->dac; } - self->ob_type->tp_free((PyObject *) self); + Py_TYPE(self)->tp_free((PyObject *) self); } @@ -196,6 +209,20 @@ extern "C" { oParams.firstChannel = 0; if (PyDict_Check(oParamsObj)) { +#if PY_MAJOR_VERSION >= 3 + if (PyDict_Contains(oParamsObj, PyUnicode_FromString("deviceId"))) { + PyObject *value = PyDict_GetItem(oParamsObj, PyUnicode_FromString("deviceId")); + oParams.deviceId = PyLong_AsLong(value); + } + if (PyDict_Contains(oParamsObj, PyUnicode_FromString("nChannels"))) { + PyObject *value = PyDict_GetItem(oParamsObj, PyUnicode_FromString("nChannels")); + oParams.nChannels = PyLong_AsLong(value); + } + if (PyDict_Contains(oParamsObj, PyUnicode_FromString("firstChannel"))) { + PyObject *value = PyDict_GetItem(oParamsObj, PyUnicode_FromString("firstChannel")); + oParams.firstChannel = PyLong_AsLong(value); + } +#else if (PyDict_Contains(oParamsObj, PyString_FromString("deviceId"))) { PyObject *value = PyDict_GetItem(oParamsObj, PyString_FromString("deviceId")); oParams.deviceId = PyInt_AsLong(value); @@ -208,6 +235,7 @@ extern "C" { PyObject *value = PyDict_GetItem(oParamsObj, PyString_FromString("firstChannel")); oParams.firstChannel = PyInt_AsLong(value); } +#endif } else { printf("First argument must be a dictionary. Default values will be used.\n"); @@ -219,6 +247,20 @@ extern "C" { iParams.firstChannel = 0; if (PyDict_Check(iParamsObj)) { +#if PY_MAJOR_VERSION >= 3 + if (PyDict_Contains(iParamsObj, PyUnicode_FromString("deviceId"))) { + PyObject *value = PyDict_GetItem(iParamsObj, PyUnicode_FromString("deviceId")); + iParams.deviceId = PyLong_AsLong(value); + } + if (PyDict_Contains(iParamsObj, PyUnicode_FromString("nChannels"))) { + PyObject *value = PyDict_GetItem(iParamsObj, PyUnicode_FromString("nChannels")); + iParams.nChannels = PyLong_AsLong(value); + } + if (PyDict_Contains(iParamsObj, PyUnicode_FromString("firstChannel"))) { + PyObject *value = PyDict_GetItem(iParamsObj, PyUnicode_FromString("firstChannel")); + iParams.firstChannel = PyLong_AsLong(value); + } +#else if (PyDict_Contains(iParamsObj, PyString_FromString("deviceId"))) { PyObject *value = PyDict_GetItem(iParamsObj, PyString_FromString("deviceId")); iParams.deviceId = PyInt_AsLong(value); @@ -231,6 +273,7 @@ extern "C" { PyObject *value = PyDict_GetItem(iParamsObj, PyString_FromString("firstChannel")); iParams.firstChannel = PyInt_AsLong(value); } +#endif } else { printf("Second argument must be a dictionary. Default values will be used.\n"); @@ -371,7 +414,11 @@ extern "C" { { if (self == NULL || self->dac == NULL) return NULL; +#if PY_MAJOR_VERSION >= 3 + return PyLong_FromLong(self->dac->getDeviceCount()); +#else return PyInt_FromLong(self->dac->getDeviceCount()); +#endif } static PyObject* RtAudio_getDeviceInfo(PyRtAudio *self, PyObject *args) @@ -397,6 +444,19 @@ extern "C" { } PyObject* obj; +#if PY_MAJOR_VERSION >= 3 + obj = PyUnicode_FromString(info.name.c_str()); + PyDict_SetItemString(info_dict, "name", obj); + + obj = PyLong_FromLong(info.outputChannels); + PyDict_SetItemString(info_dict, "outputChannels", obj); + + obj = PyLong_FromLong(info.inputChannels); + PyDict_SetItemString(info_dict, "inputChannels", obj); + + obj = PyLong_FromLong(info.duplexChannels); + PyDict_SetItemString(info_dict, "duplexChannels", obj); +#else obj = PyString_FromString(info.name.c_str()); PyDict_SetItemString(info_dict, "name", obj); @@ -408,6 +468,7 @@ extern "C" { obj = PyInt_FromLong(info.duplexChannels); PyDict_SetItemString(info_dict, "duplexChannels", obj); +#endif if (info.isDefaultOutput) { Py_INCREF(Py_True); @@ -440,13 +501,21 @@ extern "C" { static PyObject* RtAudio_getDefaultOutputDevice(PyRtAudio *self, PyObject *args) { if (self == NULL || self->dac == NULL) return NULL; +#if PY_MAJOR_VERSION >= 3 + return PyLong_FromLong(self->dac->getDefaultOutputDevice()); +#else return PyInt_FromLong(self->dac->getDefaultOutputDevice()); +#endif } static PyObject* RtAudio_getDefaultInputDevice(PyRtAudio *self, PyObject *args) { if (self == NULL || self->dac == NULL) return NULL; +#if PY_MAJOR_VERSION >= 3 + return PyLong_FromLong(self->dac->getDefaultInputDevice()); +#else return PyInt_FromLong(self->dac->getDefaultInputDevice()); +#endif } static PyObject* RtAudio_getStreamTime(PyRtAudio *self, PyObject *args) @@ -458,13 +527,21 @@ extern "C" { static PyObject* RtAudio_getStreamLatency(PyRtAudio *self, PyObject *args) { if (self == NULL || self->dac == NULL) return NULL; +#if PY_MAJOR_VERSION >= 3 + return PyLong_FromLong( self->dac->getStreamLatency() ); +#else return PyInt_FromLong( self->dac->getStreamLatency() ); +#endif } static PyObject* RtAudio_getStreamSampleRate(PyRtAudio *self, PyObject *args) { if (self == NULL || self->dac == NULL) return NULL; +#if PY_MAJOR_VERSION >= 3 + return PyLong_FromLong( self->dac->getStreamSampleRate() ); +#else return PyInt_FromLong( self->dac->getStreamSampleRate() ); +#endif } static PyObject* RtAudio_showWarnings(PyRtAudio *self, PyObject *args) @@ -526,10 +603,8 @@ extern "C" { {NULL} }; - static PyTypeObject RtAudio_type = { - PyObject_HEAD_INIT(NULL) - 0, /*ob_size*/ + PyVarObject_HEAD_INIT(NULL, 0) "rtaudio.RtAudio", /*tp_name*/ sizeof(RtAudio), /*tp_basicsize*/ 0, /*tp_itemsize*/ @@ -578,22 +653,43 @@ extern "C" { //0, /* Type attribute cache version tag. Added in version 2.6 */ }; - +#if PY_MAJOR_VERSION >= 3 + static PyModuleDef RtAudio_module = { + PyModuleDef_HEAD_INIT, + "RtAudio", + "RtAudio wrapper.", + }; +#endif #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC - initrtaudio(void) +#if PY_MAJOR_VERSION >= 3 + PyInit_rtaudio(void) +#else + initrtaudio(void) +#endif { - PyEval_InitThreads(); + if (!PyEval_ThreadsInitialized()) + PyEval_InitThreads(); if (PyType_Ready(&RtAudio_type) < 0) +#if PY_MAJOR_VERSION >= 3 + return NULL; +#else return; +#endif +#if PY_MAJOR_VERSION >= 3 + PyObject* module = PyModule_Create(&RtAudio_module); + if (module == NULL) + return NULL; +#else PyObject* module = Py_InitModule3("rtaudio", NULL, "RtAudio wrapper."); if (module == NULL) return; +#endif Py_INCREF(&RtAudio_type); PyModule_AddObject(module, "RtAudio", (PyObject *)&RtAudio_type); @@ -601,5 +697,10 @@ extern "C" { RtAudioErrorException = PyErr_NewException("rtaudio.RtError", NULL, NULL); Py_INCREF(RtAudioErrorException); PyModule_AddObject(module, "RtError", RtAudioErrorException); +#if PY_MAJOR_VERSION >= 3 + return module; +#else + return; +#endif } } diff --git a/contrib/python/pyrtaudio/setup.py b/contrib/python/pyrtaudio/setup.py index a942f3b..7c40466 100644 --- a/contrib/python/pyrtaudio/setup.py +++ b/contrib/python/pyrtaudio/setup.py @@ -40,7 +40,6 @@ elif OSNAME == 'Windows': extra_compile_args.append('-EHsc') - audio = Extension('rtaudio', sources=sources, libraries=libraries,