5353import atexit as _atexit
5454import os as _os
5555import 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
695729class _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