Skip to content

Commit 559b0d0

Browse files
committed
Switch between API and ABI modes based on an environment variable
This is probably not the correct way to do this, but it's the easiest without breaking API compatibility
1 parent 8b61f50 commit 559b0d0

File tree

4 files changed

+526
-161
lines changed

4 files changed

+526
-161
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def run(self):
9292
setup_requires=['CFFI>=1.0'],
9393
install_requires=['CFFI>=1.0'],
9494
extras_require={'NumPy': ['NumPy']},
95-
cffi_modules=['sounddevice_build.py:ffibuilder'],
95+
cffi_modules=['sounddevice_build.py:ffibuilder', 'sounddevice_build_apimode.py:ffibuilder'],
9696
author='Matthias Geier',
9797
author_email='[email protected]',
9898
description='Play and Record Sound with Python',

sounddevice.py

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,40 @@
5353
import atexit as _atexit
5454
import os as _os
5555
import sys as _sys
56-
from _sounddevice import ffi as _ffi, lib as _lib
56+
from _sounddevice import ffi as _ffi
57+
58+
59+
IS_API_MODE = 'PY_SOUNDDEVICE_API_MODE' in _os.environ
60+
61+
if IS_API_MODE:
62+
from _sounddevice import lib as _lib
63+
else:
64+
import platform as _platform
65+
from ctypes.util import find_library as _find_library
66+
67+
try:
68+
for _libname in (
69+
'portaudio', # Default name on POSIX systems
70+
'bin\\libportaudio-2.dll', # DLL from conda-forge
71+
'lib/libportaudio.dylib', # dylib from anaconda
72+
):
73+
_libname = _find_library(_libname)
74+
if _libname is not None:
75+
break
76+
else:
77+
raise OSError('PortAudio library not found')
78+
_lib = _ffi.dlopen(_libname)
79+
except OSError:
80+
if _platform.system() == 'Darwin':
81+
_libname = 'libportaudio.dylib'
82+
elif _platform.system() == 'Windows':
83+
_libname = 'libportaudio' + _platform.architecture()[0] + '.dll'
84+
else:
85+
raise
86+
import _sounddevice_data
87+
_libname = _os.path.join(
88+
next(iter(_sounddevice_data.__path__)), 'portaudio-binaries', _libname)
89+
_lib = _ffi.dlopen(_libname)
5790

5891
_sampleformats = {
5992
'float32': _lib.paFloat32,
@@ -686,10 +719,11 @@ def get_portaudio_version():
686719
return _lib.Pa_GetVersion(), _ffi.string(_lib.Pa_GetVersionText()).decode()
687720

688721

689-
@_ffi.def_extern(error=_lib.paAbort)
690-
def FFI_STREAM_CALLBACK(iptr, optr, frames, time, status, userdata):
691-
stream = _ffi.from_handle(userdata)
692-
return stream._callback(iptr, optr, frames, time, status, None)
722+
if IS_API_MODE:
723+
@_ffi.def_extern(error=_lib.paAbort)
724+
def FFI_STREAM_CALLBACK(iptr, optr, frames, time, status, userdata):
725+
stream = _ffi.from_handle(userdata)
726+
return stream._py_callback(iptr, optr, frames, time, status, None)
693727

694728

695729
class _StreamBase:
@@ -798,20 +832,28 @@ def __init__(self, kind, samplerate=None, blocksize=None, device=None,
798832
iparameters = _ffi.NULL
799833
oparameters = parameters
800834

801-
# ffi_callback = _ffi.callback('PaStreamCallback', error=_lib.paAbort)
835+
global IS_API_MODE
836+
if IS_API_MODE:
837+
def ffi_callback(fn):
838+
userdata = _ffi.new_handle(self)
839+
self._userdata_ptr = userdata
840+
self._py_callback = fn
841+
return _lib.FFI_STREAM_CALLBACK
842+
else:
843+
ffi_callback = _ffi.callback('PaStreamCallback', error=_lib.paAbort)
802844

803845
if callback is None:
804846
callback_ptr = _ffi.NULL
805847
elif kind == 'input' and wrap_callback == 'buffer':
806848

807-
# @ffi_callback
849+
@ffi_callback
808850
def callback_ptr(iptr, optr, frames, time, status, _):
809851
data = _buffer(iptr, frames, self._channels, self._samplesize)
810852
return _wrap_callback(callback, data, frames, time, status)
811853

812854
elif kind == 'input' and wrap_callback == 'array':
813855

814-
# @ffi_callback
856+
@ffi_callback
815857
def callback_ptr(iptr, optr, frames, time, status, _):
816858
data = _array(
817859
_buffer(iptr, frames, self._channels, self._samplesize),
@@ -820,14 +862,14 @@ def callback_ptr(iptr, optr, frames, time, status, _):
820862

821863
elif kind == 'output' and wrap_callback == 'buffer':
822864

823-
# @ffi_callback
865+
@ffi_callback
824866
def callback_ptr(iptr, optr, frames, time, status, _):
825867
data = _buffer(optr, frames, self._channels, self._samplesize)
826868
return _wrap_callback(callback, data, frames, time, status)
827869

828870
elif kind == 'output' and wrap_callback == 'array':
829871

830-
# @ffi_callback
872+
@ffi_callback
831873
def callback_ptr(iptr, optr, frames, time, status, _):
832874
data = _array(
833875
_buffer(optr, frames, self._channels, self._samplesize),
@@ -836,7 +878,7 @@ def callback_ptr(iptr, optr, frames, time, status, _):
836878

837879
elif kind == 'duplex' and wrap_callback == 'buffer':
838880

839-
# @ffi_callback
881+
@ffi_callback
840882
def callback_ptr(iptr, optr, frames, time, status, _):
841883
ichannels, ochannels = self._channels
842884
isize, osize = self._samplesize
@@ -847,7 +889,7 @@ def callback_ptr(iptr, optr, frames, time, status, _):
847889

848890
elif kind == 'duplex' and wrap_callback == 'array':
849891

850-
# @ffi_callback
892+
@ffi_callback
851893
def callback_ptr(iptr, optr, frames, time, status, _):
852894
ichannels, ochannels = self._channels
853895
idtype, odtype = self._dtype
@@ -865,15 +907,15 @@ def callback_ptr(iptr, optr, frames, time, status, _):
865907

866908
# CFFI callback object must be kept alive during stream lifetime:
867909
self._callback = callback_ptr
868-
# if userdata is None:
869-
# userdata = _ffi.NULL
870-
userdata = _ffi.new_handle(self)
871-
self._userdata_ptr = userdata
910+
if userdata is None:
911+
userdata = _ffi.NULL
912+
if not hasattr(self, '_userdata_ptr'):
913+
self._userdata_ptr = userdata
872914

873915
self._ptr = _ffi.new('PaStream**')
874916
_check(_lib.Pa_OpenStream(self._ptr, iparameters, oparameters,
875917
samplerate, blocksize, stream_flags,
876-
_lib.FFI_STREAM_CALLBACK, userdata),
918+
self._callback, self._userdata_ptr),
877919
f'Error opening {self.__class__.__name__}')
878920

879921
# dereference PaStream** --> PaStream*

0 commit comments

Comments
 (0)