Hello community,

here is the log from the commit of package python-SoundCard for 
openSUSE:Factory checked in at 2019-12-03 15:21:43
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-SoundCard (Old)
 and      /work/SRC/openSUSE:Factory/.python-SoundCard.new.4691 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-SoundCard"

Tue Dec  3 15:21:43 2019 rev:5 rq:752995 version:0.3.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-SoundCard/python-SoundCard.changes        
2019-04-02 09:21:36.944672817 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-SoundCard.new.4691/python-SoundCard.changes  
    2019-12-03 15:21:43.858524714 +0100
@@ -1,0 +2,15 @@
+Mon Dec  2 17:11:34 UTC 2019 - Todd R <[email protected]>
+
+- Update to 0.3.3
+  * Fix attribute error when accessing stream state
+  * adds experimental support for exclusive mode on Windows
+  * adds latency hints to the documentation
+  * fix exception when monitor is default pulseaudio device
+  * fixes deprecation warning
+  * fixes missing dtype declaration
+  * fixes sample rate conversion on macOS
+  * fixes silence recording on macOS 
+  * makes mainloop a global singleton
+  * remove useless declaration and call to _pa_stream_get_buffer_attr()
+
+-------------------------------------------------------------------

Old:
----
  SoundCard-0.3.0.tar.gz

New:
----
  SoundCard-0.3.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-SoundCard.spec ++++++
--- /var/tmp/diff_new_pack.XUzeaE/_old  2019-12-03 15:21:44.334524496 +0100
+++ /var/tmp/diff_new_pack.XUzeaE/_new  2019-12-03 15:21:44.342524492 +0100
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define         skip_python2 1
 Name:           python-SoundCard
-Version:        0.3.0
+Version:        0.3.3
 Release:        0
 Summary:        Python package to play and record audio
 License:        BSD-3-Clause

++++++ SoundCard-0.3.0.tar.gz -> SoundCard-0.3.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SoundCard-0.3.0/PKG-INFO new/SoundCard-0.3.3/PKG-INFO
--- old/SoundCard-0.3.0/PKG-INFO        2019-03-25 14:00:27.000000000 +0100
+++ new/SoundCard-0.3.3/PKG-INFO        2019-10-08 10:29:17.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: SoundCard
-Version: 0.3.0
+Version: 0.3.3
 Summary: Play and record audio without resorting to CPython extensions
 Home-page: https://github.com/bastibe/SoundCard
 Author: Bastian Bechtold
@@ -49,7 +49,6 @@
         .. |closed-issues| image:: 
https://img.shields.io/github/issues-closed/bastibe/soundcard.svg
         .. |open-prs| image:: 
https://img.shields.io/github/issues-pr/bastibe/soundcard.svg
         .. |closed-prs| image:: 
https://img.shields.io/github/issues-pr-closed/bastibe/soundcard.svg
-        .. |status| image:: https://img.shields.io/pypi/status/soundcard.svg
         
         Tutorial
         --------
@@ -104,6 +103,33 @@
                     data = mic.record(numframes=1024)
                     sp.play(data)
         
+        Latency
+        -------
+        
+        By default, SoundCard records and plays at the operating system's 
default
+        configuration. Particularly on laptops, this configuration might have 
extreme
+        latencies, up to multiple seconds.
+        
+        In order to request lower latencies, pass a ``blocksize`` to 
``player`` or
+        ``recorder``. This tells the operating system your desired latency, 
and it will
+        try to honor your request as best it can. On Windows/WASAPI, setting
+        ``exclusive_mode=True`` might help, too (this is currently 
experimental).
+        
+        Another source of latency is in the ``record`` function, which buffers 
output up
+        to the requested ``numframes``. In general, for optimal latency, you 
should use
+        a ``numframes`` significantly lower than the ``blocksize`` above, 
maybe by a
+        factor of two or four.
+        
+        To get the audio data as quickly as absolutely possible, you can use
+        ``numframes=None``, which will return whatever audio data is available 
right
+        now, without any buffering. Note that this might receive different 
numbers of
+        frames each time.
+        
+        With the above settings, block sizes of 256 samples or ten 
milliseconds are
+        usually no problem. The total latency of playback and recording is 
dependent on
+        how these buffers are handled by the operating system, though, and 
might be
+        significantly higher.
+        
         Channel Maps
         ------------
         
@@ -124,8 +150,8 @@
         * Windows/WASAPI currently records garbage if you record only a single 
channel.
           The reason for this is yet unknown. Multi-channel and channel maps 
work,
           though.
-        * Windows/WASAPI silently ignores the blocksize. Apparently, it only 
supports
-          variable block sizes in exclusive mode, which is not yet supported.
+        * Windows/WASAPI silently ignores the blocksize in some cases. 
Apparently, it
+          only supports variable block sizes in exclusive mode.
         * Error messages often report some internal CFFI/backend errors. This 
will be
           improved in the future.
         
@@ -145,6 +171,15 @@
         - 2018-11-28 adds Sphinx/Readthedocs documentation
         - 2019-03-25 adds support for Python 3.5
           (Thank you, Daniel R. Kumor!)
+        - 2019-04-29 adds experimental support for exclusive mode on Windows
+        - 2019-05-13 fixes sample rate conversion on macOS
+        - 2019-05-15 fixes silence recording on macOS
+        - 2019-06-11 fixes exception when monitoring default device on Linux
+          (Thank you, Inti Pelupessy!)
+        - 2019-06-18 fixes crash when opening many streams on Linux
+        - 2019-08-23 fixes attribute error when accessing stream state on Linux
+          (Thank you, Davíð Sindri Pétursson)
+        - 2019-10-08 fixes inconsistent dtypes when recording on Linux
         
 Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SoundCard-0.3.0/README.rst 
new/SoundCard-0.3.3/README.rst
--- old/SoundCard-0.3.0/README.rst      2019-03-25 13:59:48.000000000 +0100
+++ new/SoundCard-0.3.3/README.rst      2019-10-08 10:16:13.000000000 +0200
@@ -42,7 +42,6 @@
 .. |closed-issues| image:: 
https://img.shields.io/github/issues-closed/bastibe/soundcard.svg
 .. |open-prs| image:: 
https://img.shields.io/github/issues-pr/bastibe/soundcard.svg
 .. |closed-prs| image:: 
https://img.shields.io/github/issues-pr-closed/bastibe/soundcard.svg
-.. |status| image:: https://img.shields.io/pypi/status/soundcard.svg
 
 Tutorial
 --------
@@ -97,6 +96,33 @@
             data = mic.record(numframes=1024)
             sp.play(data)
 
+Latency
+-------
+
+By default, SoundCard records and plays at the operating system's default
+configuration. Particularly on laptops, this configuration might have extreme
+latencies, up to multiple seconds.
+
+In order to request lower latencies, pass a ``blocksize`` to ``player`` or
+``recorder``. This tells the operating system your desired latency, and it will
+try to honor your request as best it can. On Windows/WASAPI, setting
+``exclusive_mode=True`` might help, too (this is currently experimental).
+
+Another source of latency is in the ``record`` function, which buffers output 
up
+to the requested ``numframes``. In general, for optimal latency, you should use
+a ``numframes`` significantly lower than the ``blocksize`` above, maybe by a
+factor of two or four.
+
+To get the audio data as quickly as absolutely possible, you can use
+``numframes=None``, which will return whatever audio data is available right
+now, without any buffering. Note that this might receive different numbers of
+frames each time.
+
+With the above settings, block sizes of 256 samples or ten milliseconds are
+usually no problem. The total latency of playback and recording is dependent on
+how these buffers are handled by the operating system, though, and might be
+significantly higher.
+
 Channel Maps
 ------------
 
@@ -117,8 +143,8 @@
 * Windows/WASAPI currently records garbage if you record only a single channel.
   The reason for this is yet unknown. Multi-channel and channel maps work,
   though.
-* Windows/WASAPI silently ignores the blocksize. Apparently, it only supports
-  variable block sizes in exclusive mode, which is not yet supported.
+* Windows/WASAPI silently ignores the blocksize in some cases. Apparently, it
+  only supports variable block sizes in exclusive mode.
 * Error messages often report some internal CFFI/backend errors. This will be
   improved in the future.
 
@@ -138,3 +164,12 @@
 - 2018-11-28 adds Sphinx/Readthedocs documentation
 - 2019-03-25 adds support for Python 3.5
   (Thank you, Daniel R. Kumor!)
+- 2019-04-29 adds experimental support for exclusive mode on Windows
+- 2019-05-13 fixes sample rate conversion on macOS
+- 2019-05-15 fixes silence recording on macOS
+- 2019-06-11 fixes exception when monitoring default device on Linux
+  (Thank you, Inti Pelupessy!)
+- 2019-06-18 fixes crash when opening many streams on Linux
+- 2019-08-23 fixes attribute error when accessing stream state on Linux
+  (Thank you, Davíð Sindri Pétursson)
+- 2019-10-08 fixes inconsistent dtypes when recording on Linux
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SoundCard-0.3.0/SoundCard.egg-info/PKG-INFO 
new/SoundCard-0.3.3/SoundCard.egg-info/PKG-INFO
--- old/SoundCard-0.3.0/SoundCard.egg-info/PKG-INFO     2019-03-25 
14:00:27.000000000 +0100
+++ new/SoundCard-0.3.3/SoundCard.egg-info/PKG-INFO     2019-10-08 
10:29:17.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: SoundCard
-Version: 0.3.0
+Version: 0.3.3
 Summary: Play and record audio without resorting to CPython extensions
 Home-page: https://github.com/bastibe/SoundCard
 Author: Bastian Bechtold
@@ -49,7 +49,6 @@
         .. |closed-issues| image:: 
https://img.shields.io/github/issues-closed/bastibe/soundcard.svg
         .. |open-prs| image:: 
https://img.shields.io/github/issues-pr/bastibe/soundcard.svg
         .. |closed-prs| image:: 
https://img.shields.io/github/issues-pr-closed/bastibe/soundcard.svg
-        .. |status| image:: https://img.shields.io/pypi/status/soundcard.svg
         
         Tutorial
         --------
@@ -104,6 +103,33 @@
                     data = mic.record(numframes=1024)
                     sp.play(data)
         
+        Latency
+        -------
+        
+        By default, SoundCard records and plays at the operating system's 
default
+        configuration. Particularly on laptops, this configuration might have 
extreme
+        latencies, up to multiple seconds.
+        
+        In order to request lower latencies, pass a ``blocksize`` to 
``player`` or
+        ``recorder``. This tells the operating system your desired latency, 
and it will
+        try to honor your request as best it can. On Windows/WASAPI, setting
+        ``exclusive_mode=True`` might help, too (this is currently 
experimental).
+        
+        Another source of latency is in the ``record`` function, which buffers 
output up
+        to the requested ``numframes``. In general, for optimal latency, you 
should use
+        a ``numframes`` significantly lower than the ``blocksize`` above, 
maybe by a
+        factor of two or four.
+        
+        To get the audio data as quickly as absolutely possible, you can use
+        ``numframes=None``, which will return whatever audio data is available 
right
+        now, without any buffering. Note that this might receive different 
numbers of
+        frames each time.
+        
+        With the above settings, block sizes of 256 samples or ten 
milliseconds are
+        usually no problem. The total latency of playback and recording is 
dependent on
+        how these buffers are handled by the operating system, though, and 
might be
+        significantly higher.
+        
         Channel Maps
         ------------
         
@@ -124,8 +150,8 @@
         * Windows/WASAPI currently records garbage if you record only a single 
channel.
           The reason for this is yet unknown. Multi-channel and channel maps 
work,
           though.
-        * Windows/WASAPI silently ignores the blocksize. Apparently, it only 
supports
-          variable block sizes in exclusive mode, which is not yet supported.
+        * Windows/WASAPI silently ignores the blocksize in some cases. 
Apparently, it
+          only supports variable block sizes in exclusive mode.
         * Error messages often report some internal CFFI/backend errors. This 
will be
           improved in the future.
         
@@ -145,6 +171,15 @@
         - 2018-11-28 adds Sphinx/Readthedocs documentation
         - 2019-03-25 adds support for Python 3.5
           (Thank you, Daniel R. Kumor!)
+        - 2019-04-29 adds experimental support for exclusive mode on Windows
+        - 2019-05-13 fixes sample rate conversion on macOS
+        - 2019-05-15 fixes silence recording on macOS
+        - 2019-06-11 fixes exception when monitoring default device on Linux
+          (Thank you, Inti Pelupessy!)
+        - 2019-06-18 fixes crash when opening many streams on Linux
+        - 2019-08-23 fixes attribute error when accessing stream state on Linux
+          (Thank you, Davíð Sindri Pétursson)
+        - 2019-10-08 fixes inconsistent dtypes when recording on Linux
         
 Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SoundCard-0.3.0/setup.py new/SoundCard-0.3.3/setup.py
--- old/SoundCard-0.3.0/setup.py        2019-03-25 13:58:21.000000000 +0100
+++ new/SoundCard-0.3.3/setup.py        2019-10-08 10:10:49.000000000 +0200
@@ -2,7 +2,7 @@
 
 setup(
     name='SoundCard',
-    version='0.3.0',
+    version='0.3.3',
     description='Play and record audio without resorting to CPython 
extensions',
     author='Bastian Bechtold',
     url='https://github.com/bastibe/SoundCard',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SoundCard-0.3.0/soundcard/coreaudio.py 
new/SoundCard-0.3.3/soundcard/coreaudio.py
--- old/SoundCard-0.3.0/soundcard/coreaudio.py  2018-11-29 09:48:16.000000000 
+0100
+++ new/SoundCard-0.3.3/soundcard/coreaudio.py  2019-10-08 10:01:45.000000000 
+0200
@@ -755,7 +755,7 @@
         converted_data = numpy.concatenate(self.queue)
         self.queue.clear()
 
-        return converted_data
+        return converted_data.reshape([-1, self.channels])
 
     def __del__(self):
         _au.AudioConverterDispose(self.audioconverter[0])
@@ -781,7 +781,7 @@
 
     def __enter__(self):
         self._queue = collections.deque()
-        self._pending_chunk = numpy.zeros([0])
+        self._pending_chunk = numpy.zeros([0, self._au.channels])
 
         channels = self._au.channels
         au = self._au.ptr[0]
@@ -793,8 +793,7 @@
             bufferlist.mNumberBuffers = 1
             bufferlist.mBuffers[0].mNumberChannels = channels
             bufferlist.mBuffers[0].mDataByteSize = numframes * 4 * channels
-            data = _ffi.new("Float32[]", numframes * channels)
-            bufferlist.mBuffers[0].mData = data
+            bufferlist.mBuffers[0].mData = _ffi.NULL
 
             status = _au.AudioUnitRender(au,
                                          actionflags,
@@ -803,10 +802,21 @@
                                          numframes,
                                          bufferlist)
 
+            # special case if output is silence:
+            if (actionflags[0] == _cac.kAudioUnitRenderAction_OutputIsSilence
+                and status == _cac.kAudioUnitErr_CannotDoInCurrentContext):
+                actionflags[0] = 0 # reset actionflags
+                status = 0 # reset error code
+                data = numpy.zeros([numframes, channels], 'float32')
+            else:
+                data = 
numpy.frombuffer(_ffi.buffer(bufferlist.mBuffers[0].mData,
+                                                    
bufferlist.mBuffers[0].mDataByteSize),
+                                        dtype='float32')
+                data = data.reshape([-1, 
bufferlist.mBuffers[0].mNumberChannels]).copy()
+
             if status != 0:
                 print('error during recording:', status)
 
-            data = numpy.frombuffer(_ffi.buffer(data), dtype='float32')
             self._queue.append(data)
             self._record_event.set()
             return status
@@ -829,7 +839,12 @@
         while not self._queue:
             self._record_event.wait()
             self._record_event.clear()
-        return self._queue.popleft()
+        block = self._queue.popleft()
+
+        # perform sample rate conversion:
+        if self._au.resample != 1:
+            block = self._resampler.resample(block)
+        return block
 
     def record(self, numframes=None):
         """Record a block of audio data.
@@ -853,30 +868,19 @@
 
         if numframes is None:
             blocks = [self._pending_chunk, self._record_chunk()]
-            self._pending_chunk = numpy.zeros([0])
+            self._pending_chunk = numpy.zeros([0, self._au.channels])
         else:
             blocks = [self._pending_chunk]
-            self._pending_chunk = numpy.zeros([0])
+            self._pending_chunk = numpy.zeros([0, self._au.channels])
             recorded_frames = len(blocks[0])
-            required_frames = 
int(numframes/self._au.resample)*self._au.channels
-            while recorded_frames < required_frames:
+            while recorded_frames < numframes:
                 block = self._record_chunk()
                 blocks.append(block)
                 recorded_frames += len(block)
-            if recorded_frames > required_frames:
-                to_split = -(recorded_frames-required_frames)
+            if recorded_frames > numframes:
+                to_split = -(recorded_frames-numframes)
                 blocks[-1], self._pending_chunk = numpy.split(blocks[-1], 
[to_split])
-
-        data = numpy.concatenate(blocks)
-
-        if self._au.channels != 1:
-            data = data.reshape([-1, self._au.channels])
-
-        if self._au.resample != 1:
-            data = self._resampler.resample(data)
-
-        if self._au.channels != 1:
-            data = data.reshape([-1, self._au.channels])
+        data = numpy.concatenate(blocks, axis=0)
 
         return data
 
@@ -887,5 +891,5 @@
 
         """
         last_chunk = numpy.reshape(self._pending_chunk, [-1, 
self._au.channels])
-        self._pending_chunk = numpy.zeros([0])
+        self._pending_chunk = numpy.zeros([0, self._au.channels])
         return last_chunk
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SoundCard-0.3.0/soundcard/coreaudioconstants.py 
new/SoundCard-0.3.3/soundcard/coreaudioconstants.py
--- old/SoundCard-0.3.0/soundcard/coreaudioconstants.py 2017-10-25 
10:58:42.000000000 +0200
+++ new/SoundCard-0.3.3/soundcard/coreaudioconstants.py 2019-10-08 
10:01:45.000000000 +0200
@@ -132,6 +132,15 @@
 kAudioUnitProperty_ElementName = 30
 kAudioUnitProperty_NickName = 54
 
+kAudioUnitRenderAction_PreRender = 1 << 2
+kAudioUnitRenderAction_PostRender = 1 << 3
+kAudioUnitRenderAction_OutputIsSilence = 1 << 4
+kAudioOfflineUnitRenderAction_Preflight = 1 << 5
+kAudioOfflineUnitRenderAction_Render = 1 << 6
+kAudioOfflineUnitRenderAction_Complete = 1 << 7
+kAudioUnitRenderAction_PostRenderError = 1 << 8
+kAudioUnitRenderAction_DoNotCheckRenderArgs = 1 << 9
+
 kAudioUnitScope_Global = 0 # The context for audio unit characteristics that 
apply to the audio unit as a whole
 kAudioUnitScope_Input = 1 # The context for audio data coming into an audio 
unit
 kAudioUnitScope_Output = 2 # The context for audio data leaving an audio unit
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SoundCard-0.3.0/soundcard/mediafoundation.py 
new/SoundCard-0.3.3/soundcard/mediafoundation.py
--- old/SoundCard-0.3.0/soundcard/mediafoundation.py    2019-03-20 
09:38:16.000000000 +0100
+++ new/SoundCard-0.3.3/soundcard/mediafoundation.py    2019-10-08 
10:01:45.000000000 +0200
@@ -410,10 +410,10 @@
     def __repr__(self):
         return '<Speaker {} ({} channels)>'.format(self.name,self.channels)
 
-    def player(self, samplerate, channels=None, blocksize=None):
+    def player(self, samplerate, channels=None, blocksize=None, 
exclusive_mode=False):
         if channels is None:
             channels = self.channels
-        return _Player(self._audio_client(), samplerate, channels, blocksize, 
False)
+        return _Player(self._audio_client(), samplerate, channels, blocksize, 
False, exclusive_mode)
 
     def play(self, data, samplerate, channels=None, blocksize=None):
         with self.player(samplerate, channels, blocksize) as p:
@@ -444,10 +444,10 @@
         else:
             return '<Microphone {} ({} 
channels)>'.format(self.name,self.channels)
 
-    def recorder(self, samplerate, channels=None, blocksize=None):
+    def recorder(self, samplerate, channels=None, blocksize=None, 
exclusive_mode=False):
         if channels is None:
             channels = self.channels
-        return _Recorder(self._audio_client(), samplerate, channels, 
blocksize, self.isloopback)
+        return _Recorder(self._audio_client(), samplerate, channels, 
blocksize, self.isloopback, exclusive_mode)
 
     def record(self, numframes, samplerate, channels=None, blocksize=None):
         with self.recorder(samplerate, channels, blocksize) as r:
@@ -463,7 +463,7 @@
 
     """
 
-    def __init__(self, ptr, samplerate, channels, blocksize, isloopback):
+    def __init__(self, ptr, samplerate, channels, blocksize, isloopback, 
exclusive_mode=False):
         self._ptr = ptr
 
         if isinstance(channels, int):
@@ -510,7 +510,10 @@
         # does not work:
         # ppMixFormat[0][0].dwChannelMask=channelmask
 
-        sharemode = _combase.AUDCLNT_SHAREMODE_SHARED
+        if exclusive_mode:
+            sharemode = _combase.AUDCLNT_SHAREMODE_EXCLUSIVE
+        else:
+            sharemode = _combase.AUDCLNT_SHAREMODE_SHARED
         #             resample   | remix      | better-SRC | nopersist
         streamflags = 0x00100000 | 0x80000000 | 0x08000000 | 0x00080000
         if isloopback:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SoundCard-0.3.0/soundcard/pulseaudio.py 
new/SoundCard-0.3.3/soundcard/pulseaudio.py
--- old/SoundCard-0.3.0/soundcard/pulseaudio.py 2019-03-20 09:38:16.000000000 
+0100
+++ new/SoundCard-0.3.3/soundcard/pulseaudio.py 2019-10-08 10:07:30.000000000 
+0200
@@ -1,4 +1,11 @@
 import os
+import atexit
+import collections
+import time
+import re
+import threading
+import warnings
+import numpy
 import cffi
 
 _ffi = cffi.FFI()
@@ -8,13 +15,196 @@
 
 _pa = _ffi.dlopen('pulse')
 
-import collections
-import time
-import re
-import numpy
-import threading
-import warnings
+# First, we need to define a global _PulseAudio proxy for interacting
+# with the C API:
 
+def _lock(func):
+    """Call a pulseaudio function while holding the mainloop lock."""
+    def func_with_lock(*args, **kwargs):
+        self = args[0]
+        with self._lock_mainloop():
+            return func(*args[1:], **kwargs)
+    return func_with_lock
+
+
+def _lock_and_block(func):
+    """Call a pulseaudio function while holding the mainloop lock, and
+       block until the operation has finished.
+
+    Use this for pulseaudio functions that return a `pa_operation *`.
+
+    """
+    def func_with_lock(*args, **kwargs):
+        self = args[0]
+        with self._lock_mainloop():
+            operation = func(*args[1:], **kwargs)
+        self._block_operation(operation)
+        self._pa_operation_unref(operation)
+    return func_with_lock
+
+class _PulseAudio:
+    """Proxy for communcation with Pulseaudio.
+
+    This holds the pulseaudio main loop, and a pulseaudio context.
+    Together, these provide the building blocks for interacting with
+    pulseaudio.
+
+    This can be used to query the pulseaudio server for sources,
+    sinks, and server information, and provides thread-safe access to
+    the main pulseaudio functions.
+
+    Any function that would return a `pa_operation *` in pulseaudio
+    will block until the operation has finished.
+
+    """
+
+    def __init__(self):
+        # these functions are called before the mainloop starts, so we
+        # don't need to hold the lock:
+        self.mainloop = _pa.pa_threaded_mainloop_new()
+        self.mainloop_api = _pa.pa_threaded_mainloop_get_api(self.mainloop)
+        self.context = _pa.pa_context_new(self.mainloop_api, b"audio")
+        _pa.pa_context_connect(self.context, _ffi.NULL, 
_pa.PA_CONTEXT_NOFLAGS, _ffi.NULL)
+        _pa.pa_threaded_mainloop_start(self.mainloop)
+
+        while self._pa_context_get_state(self.context) != _pa.PA_CONTEXT_READY:
+            time.sleep(0.001)
+
+    def _shutdown(self):
+        operation = self._pa_context_drain(self.context, _ffi.NULL, _ffi.NULL)
+        self._block_operation(operation)
+        self._pa_context_disconnect(self.context)
+        self._pa_context_unref(self.context)
+        # no more mainloop locking necessary from here on:
+        _pa.pa_threaded_mainloop_stop(self.mainloop)
+        _pa.pa_threaded_mainloop_free(self.mainloop)
+
+    def _block_operation(self, operation):
+        """Wait until the operation has finished."""
+        if operation == _ffi.NULL:
+            return
+        while self._pa_operation_get_state(operation) == 
_pa.PA_OPERATION_RUNNING:
+            time.sleep(0.001)
+
+    @property
+    def source_list(self):
+        """Return a list of dicts of information about available sources."""
+        info = []
+        @_ffi.callback("pa_source_info_cb_t")
+        def callback(context, source_info, eol, userdata):
+            if not eol:
+                
info.append(dict(name=_ffi.string(source_info.description).decode('utf-8'),
+                                 
id=_ffi.string(source_info.name).decode('utf-8')))
+        self._pa_context_get_source_info_list(self.context, callback, 
_ffi.NULL)
+        return info
+
+    def source_info(self, id):
+        """Return a dictionary of information about a specific source."""
+        info = []
+        @_ffi.callback("pa_source_info_cb_t")
+        def callback(context, source_info, eol, userdata):
+            if not eol:
+                info_dict = dict(latency=source_info.latency,
+                                 
configured_latency=source_info.configured_latency,
+                                 channels=source_info.sample_spec.channels,
+                                 
name=_ffi.string(source_info.description).decode('utf-8'))
+                for prop in ['device.class', 'device.api', 'device.bus']:
+                    data = _pa.pa_proplist_gets(source_info.proplist, 
prop.encode())
+                    info_dict[prop] = _ffi.string(data).decode('utf-8') if 
data else None
+                info.append(info_dict)
+
+        self._pa_context_get_source_info_by_name(self.context, id.encode(), 
callback, _ffi.NULL)
+        return info[0]
+
+    @property
+    def sink_list(self):
+        """Return a list of dicts of information about available sinks."""
+        info = []
+        @_ffi.callback("pa_sink_info_cb_t")
+        def callback(context, sink_info, eol, userdata):
+            if not eol:
+                
info.append((dict(name=_ffi.string(sink_info.description).decode('utf-8'),
+                                  
id=_ffi.string(sink_info.name).decode('utf-8'))))
+        self._pa_context_get_sink_info_list(self.context, callback, _ffi.NULL)
+        return info
+
+    def sink_info(self, id):
+        """Return a dictionary of information about a specific sink."""
+        info = []
+        @_ffi.callback("pa_sink_info_cb_t")
+        def callback(context, sink_info, eol, userdata):
+            if not eol:
+                info_dict = dict(latency=sink_info.latency,
+                                 
configured_latency=sink_info.configured_latency,
+                                 channels=sink_info.sample_spec.channels,
+                                 
name=_ffi.string(sink_info.description).decode('utf-8'))
+                for prop in ['device.class', 'device.api', 'device.bus']:
+                    data = _pa.pa_proplist_gets(sink_info.proplist, 
prop.encode())
+                    info_dict[prop] = _ffi.string(data).decode('utf-8') if 
data else None
+                info.append(info_dict)
+        self._pa_context_get_sink_info_by_name(self.context, id.encode(), 
callback, _ffi.NULL)
+        return info[0]
+
+    @property
+    def server_info(self):
+        """Return a dictionary of information about the server."""
+        info = {}
+        @_ffi.callback("pa_server_info_cb_t")
+        def callback(context, server_info, userdata):
+            info['server version'] = 
_ffi.string(server_info.server_version).decode('utf-8')
+            info['server name'] = 
_ffi.string(server_info.server_name).decode('utf-8')
+            info['default sink id'] = 
_ffi.string(server_info.default_sink_name).decode('utf-8')
+            info['default source id'] = 
_ffi.string(server_info.default_source_name).decode('utf-8')
+        self._pa_context_get_server_info(self.context, callback, _ffi.NULL)
+        return info
+
+    def _lock_mainloop(self):
+        """Context manager for locking the mainloop.
+
+        Hold this lock before calling any pulseaudio function while
+        the mainloop is running.
+
+        """
+
+        class Lock():
+            def __enter__(self_):
+                _pa.pa_threaded_mainloop_lock(self.mainloop)
+            def __exit__(self_, exc_type, exc_value, traceback):
+                _pa.pa_threaded_mainloop_unlock(self.mainloop)
+        return Lock()
+
+    # create thread-safe versions of all used pulseaudio functions:
+    _pa_context_get_source_info_list = 
_lock_and_block(_pa.pa_context_get_source_info_list)
+    _pa_context_get_source_info_by_name = 
_lock_and_block(_pa.pa_context_get_source_info_by_name)
+    _pa_context_get_sink_info_list = 
_lock_and_block(_pa.pa_context_get_sink_info_list)
+    _pa_context_get_sink_info_by_name = 
_lock_and_block(_pa.pa_context_get_sink_info_by_name)
+    _pa_context_get_server_info = 
_lock_and_block(_pa.pa_context_get_server_info)
+    _pa_context_get_state = _lock(_pa.pa_context_get_state)
+    _pa_context_drain = _lock(_pa.pa_context_drain)
+    _pa_context_disconnect = _lock(_pa.pa_context_disconnect)
+    _pa_context_unref = _lock(_pa.pa_context_unref)
+    _pa_operation_get_state = _lock(_pa.pa_operation_get_state)
+    _pa_operation_unref = _lock(_pa.pa_operation_unref)
+    _pa_stream_get_state = _lock(_pa.pa_stream_get_state)
+    _pa_sample_spec_valid = _lock(_pa.pa_sample_spec_valid)
+    _pa_stream_new = _lock(_pa.pa_stream_new)
+    _pa_stream_get_channel_map = _lock(_pa.pa_stream_get_channel_map)
+    _pa_stream_drain = _lock_and_block(_pa.pa_stream_drain)
+    _pa_stream_disconnect = _lock(_pa.pa_stream_disconnect)
+    _pa_stream_unref = _lock(_pa.pa_stream_unref)
+    _pa_stream_connect_record = _lock(_pa.pa_stream_connect_record)
+    _pa_stream_readable_size = _lock(_pa.pa_stream_readable_size)
+    _pa_stream_peek = _lock(_pa.pa_stream_peek)
+    _pa_stream_drop = _lock(_pa.pa_stream_drop)
+    _pa_stream_connect_playback = _lock(_pa.pa_stream_connect_playback)
+    _pa_stream_update_timing_info = 
_lock_and_block(_pa.pa_stream_update_timing_info)
+    _pa_stream_get_latency = _lock(_pa.pa_stream_get_latency)
+    _pa_stream_writable_size = _lock(_pa.pa_stream_writable_size)
+    _pa_stream_write = _lock(_pa.pa_stream_write)
+    _pa_stream_set_read_callback = _pa.pa_stream_set_read_callback
+
+_pulse = _PulseAudio()
+atexit.register(_pulse._shutdown)
 
 def all_speakers():
     """A list of all connected speakers.
@@ -24,8 +214,7 @@
     speakers : list(_Speaker)
 
     """
-    with _PulseAudio() as p:
-        return [_Speaker(id=s['id']) for s in p.sink_list]
+    return [_Speaker(id=s['id']) for s in _pulse.sink_list]
 
 
 def default_speaker():
@@ -36,9 +225,8 @@
     speaker : _Speaker
 
     """
-    with _PulseAudio() as p:
-        name = p.server_info['default sink id']
-        return get_speaker(name)
+    name = _pulse.server_info['default sink id']
+    return get_speaker(name)
 
 
 def get_speaker(id):
@@ -55,8 +243,7 @@
     speaker : _Speaker
 
     """
-    with _PulseAudio() as p:
-        speakers = p.sink_list
+    speakers = _pulse.sink_list
     return _Speaker(id=_match_soundcard(id, speakers)['id'])
 
 
@@ -83,12 +270,11 @@
         warnings.warn("The exclude_monitors flag is being replaced by the 
include_loopback flag", DeprecationWarning)
         include_loopback = not exclude_monitors
 
-    with _PulseAudio() as p:
-        mics = [_Microphone(id=m['id']) for m in p.source_list]
-        if not include_loopback:
-            return [m for m in mics if m._get_info()['device.class'] != 
'monitor']
-        else:
-            return mics
+    mics = [_Microphone(id=m['id']) for m in _pulse.source_list]
+    if not include_loopback:
+        return [m for m in mics if m._get_info()['device.class'] != 'monitor']
+    else:
+        return mics
 
 
 def default_microphone():
@@ -98,9 +284,8 @@
     -------
     microphone : _Microphone
     """
-    with _PulseAudio() as p:
-        name = p.server_info['default source id']
-        return get_microphone(name)
+    name = _pulse.server_info['default source id']
+    return get_microphone(name, include_loopback=True)
 
 
 def get_microphone(id, include_loopback=False, exclude_monitors=True):
@@ -128,8 +313,7 @@
         warnings.warn("The exclude_monitors flag is being replaced by the 
include_loopback flag", DeprecationWarning)
         include_loopback = not exclude_monitors
 
-    with _PulseAudio() as p:
-        microphones = p.source_list
+    microphones = _pulse.source_list
     return _Microphone(id=_match_soundcard(id, microphones, 
include_loopback)['id'])
 
 
@@ -186,8 +370,7 @@
         return self._get_info()['name']
 
     def _get_info(self):
-        with _PulseAudio() as p:
-            return p.source_info(self._id)
+        return _pulse.source_info(self._id)
 
 
 class _Speaker(_SoundCard):
@@ -222,6 +405,10 @@
         blocksize : int
             Will play this many samples at a time. Choose a lower
             block size for lower latency and more CPU usage.
+        exclusive_mode : bool, optional
+            Windows only: open sound card in exclusive mode, which
+            might be necessary for short block lengths or high
+            sample rates or optimal performance. Default is ``False``.
 
         Returns
         -------
@@ -256,8 +443,7 @@
             s.play(data)
 
     def _get_info(self):
-        with _PulseAudio() as p:
-            return p.sink_info(self._id)
+        return _pulse.sink_info(self._id)
 
 
 class _Microphone(_SoundCard):
@@ -300,6 +486,10 @@
         blocksize : int
             Will record this many samples at a time. Choose a lower
             block size for lower latency and more CPU usage.
+        exclusive_mode : bool, optional
+            Windows only: open sound card in exclusive mode, which
+            might be necessary for short block lengths or high
+            sample rates or optimal performance. Default is ``False``.
 
         Returns
         -------
@@ -359,9 +549,6 @@
         self.channels = channels
 
     def __enter__(self):
-        self._pulse = _PulseAudio()
-        self._pulse.__enter__()
-
         samplespec = _ffi.new("pa_sample_spec*")
         samplespec.format = _pa.PA_SAMPLE_FLOAT32LE
         samplespec.rate = self._samplerate
@@ -371,7 +558,7 @@
             samplespec.channels = self.channels
         else:
             raise TypeError('channels must be iterable or integer')
-        if not self._pulse._pa_sample_spec_valid(samplespec):
+        if not _pulse._pa_sample_spec_valid(samplespec):
             raise RuntimeError('invalid sample spec')
 
         channelmap = _ffi.new("pa_channel_map*")
@@ -382,7 +569,7 @@
         if not _pa.pa_channel_map_valid(channelmap):
             raise RuntimeError('invalid channel map')
 
-        self.stream = self._pulse._pa_stream_new(self._pulse.context, 
self._name.encode(), samplespec, channelmap)
+        self.stream = _pulse._pa_stream_new(_pulse.context, 
self._name.encode(), samplespec, channelmap)
         bufattr = _ffi.new("pa_buffer_attr*")
         bufattr.maxlength = 2**32-1 # max buffer length
         numchannels = self.channels if isinstance(self.channels, int) else 
len(self.channels)
@@ -391,33 +578,29 @@
         bufattr.prebuf = 2**32-1 # start playback after this bytes are 
available
         bufattr.tlength = self._blocksize*numchannels*4 if self._blocksize 
else 2**32-1 # buffer length in bytes on server
         self._connect_stream(bufattr)
-        while self._pulse._pa_stream_get_state(self.stream) not in 
[_pa.PA_STREAM_READY, _pa.PA_STREAM_FAILED]:
+        while _pulse._pa_stream_get_state(self.stream) not in 
[_pa.PA_STREAM_READY, _pa.PA_STREAM_FAILED]:
             time.sleep(0.01)
-        if self._pulse._pa_stream_get_state(self.stream) == 
_pa.PA_STREAM_FAILED:
+        if _pulse._pa_stream_get_state(self.stream) == _pa.PA_STREAM_FAILED:
             raise RuntimeError('Stream creation failed. Stream is in status {}'
-                               
.format(self._pulse.pa_stream_get_state(self.stream)))
-        channel_map = self._pulse._pa_stream_get_channel_map(self.stream)
+                               
.format(_pulse._pa_stream_get_state(self.stream)))
+        channel_map = _pulse._pa_stream_get_channel_map(self.stream)
         self.channels = int(channel_map.channels)
         return self
 
     def __exit__(self, exc_type, exc_value, traceback):
-        try:
-            if isinstance(self, _Player): # only playback streams need to drain
-                self._pulse._pa_stream_drain(self.stream, _ffi.NULL, _ffi.NULL)
-            self._pulse._pa_stream_disconnect(self.stream)
-            while self._pulse._pa_stream_get_state(self.stream) != 
_pa.PA_STREAM_TERMINATED:
-                time.sleep(0.01)
-            self._pulse._pa_stream_unref(self.stream)
-        finally:
-            # make sure that this definitely gets called no matter what:
-            self._pulse.__exit__(exc_type, exc_value, traceback)
+        if isinstance(self, _Player): # only playback streams need to drain
+            _pulse._pa_stream_drain(self.stream, _ffi.NULL, _ffi.NULL)
+        _pulse._pa_stream_disconnect(self.stream)
+        while _pulse._pa_stream_get_state(self.stream) != 
_pa.PA_STREAM_TERMINATED:
+            time.sleep(0.01)
+        _pulse._pa_stream_unref(self.stream)
 
     @property
     def latency(self):
         """float : Latency of the stream in seconds (only available on 
Linux)"""
-        self._pulse._pa_stream_update_timing_info(self.stream, _ffi.NULL, 
_ffi.NULL)
+        _pulse._pa_stream_update_timing_info(self.stream, _ffi.NULL, _ffi.NULL)
         microseconds = _ffi.new("pa_usec_t*")
-        self._pulse._pa_stream_get_latency(self.stream, microseconds, 
_ffi.NULL)
+        _pulse._pa_stream_get_latency(self.stream, microseconds, _ffi.NULL)
         return microseconds[0] / 1000000 # 1_000_000 (3.5 compat)
 
 
@@ -435,7 +618,7 @@
     """
 
     def _connect_stream(self, bufattr):
-        self._pulse._pa_stream_connect_playback(self.stream, 
self._id.encode(), bufattr, _pa.PA_STREAM_ADJUST_LATENCY,
+        _pulse._pa_stream_connect_playback(self.stream, self._id.encode(), 
bufattr, _pa.PA_STREAM_ADJUST_LATENCY,
                                                 _ffi.NULL, _ffi.NULL)
 
     def play(self, data):
@@ -472,14 +655,13 @@
             data = numpy.tile(data, [1, self.channels])
         if data.shape[1] != self.channels:
             raise TypeError('second dimension of data must be equal to the 
number of channels, not {}'.format(data.shape[1]))
-        bufattr = self._pulse._pa_stream_get_buffer_attr(self.stream)
         while data.nbytes > 0:
-            nwrite = self._pulse._pa_stream_writable_size(self.stream) // 4
+            nwrite = _pulse._pa_stream_writable_size(self.stream) // 4
             if nwrite == 0:
                 time.sleep(0.001)
                 continue
             bytes = data[:nwrite].ravel().tostring()
-            self._pulse._pa_stream_write(self.stream, bytes, len(bytes), 
_ffi.NULL, 0, _pa.PA_SEEK_RELATIVE)
+            _pulse._pa_stream_write(self.stream, bytes, len(bytes), _ffi.NULL, 
0, _pa.PA_SEEK_RELATIVE)
             data = data[nwrite:]
 
 class _Recorder(_Stream):
@@ -497,16 +679,16 @@
 
     def __init__(self, *args, **kwargs):
         super(_Recorder, self).__init__(*args, **kwargs)
-        self._pending_chunk = numpy.zeros((0, ))
+        self._pending_chunk = numpy.zeros((0, ), dtype='float32')
         self._record_event = threading.Event()
 
     def _connect_stream(self, bufattr):
-        self._pulse._pa_stream_connect_record(self.stream, self._id.encode(), 
bufattr, _pa.PA_STREAM_ADJUST_LATENCY)
+        _pulse._pa_stream_connect_record(self.stream, self._id.encode(), 
bufattr, _pa.PA_STREAM_ADJUST_LATENCY)
         @_ffi.callback("pa_stream_request_cb_t")
         def read_callback(stream, nbytes, userdata):
             self._record_event.set()
         self._callback = read_callback
-        self._pulse._pa_stream_set_read_callback(self.stream, read_callback, 
_ffi.NULL)
+        _pulse._pa_stream_set_read_callback(self.stream, read_callback, 
_ffi.NULL)
 
     def _record_chunk(self):
         '''Record one chunk of audio data, as returned by pulseaudio
@@ -517,21 +699,21 @@
         '''
         data_ptr = _ffi.new('void**')
         nbytes_ptr = _ffi.new('size_t*')
-        readable_bytes = self._pulse._pa_stream_readable_size(self.stream)
+        readable_bytes = _pulse._pa_stream_readable_size(self.stream)
         while not readable_bytes:
             self._record_event.wait()
             self._record_event.clear()
-            readable_bytes = self._pulse._pa_stream_readable_size(self.stream)
+            readable_bytes = _pulse._pa_stream_readable_size(self.stream)
         data_ptr[0] = _ffi.NULL
         nbytes_ptr[0] = 0
-        self._pulse._pa_stream_peek(self.stream, data_ptr, nbytes_ptr)
+        _pulse._pa_stream_peek(self.stream, data_ptr, nbytes_ptr)
         if data_ptr[0] != _ffi.NULL:
             buffer = _ffi.buffer(data_ptr[0], nbytes_ptr[0])
             chunk = numpy.frombuffer(buffer, dtype='float32').copy()
         if data_ptr[0] == _ffi.NULL and nbytes_ptr[0] != 0:
             chunk = numpy.zeros(nbytes_ptr[0]//4, dtype='float32')
         if nbytes_ptr[0] > 0:
-            self._pulse._pa_stream_drop(self.stream)
+            _pulse._pa_stream_drop(self.stream)
             return chunk
 
     def record(self, numframes=None):
@@ -597,203 +779,5 @@
 
         """
         last_chunk = numpy.reshape(self._pending_chunk, [-1, self.channels])
-        self._pending_chunk = numpy.zeros((0, ))
+        self._pending_chunk = numpy.zeros((0, ), dtype='float32')
         return last_chunk
-
-
-def _lock(func):
-    """Call a pulseaudio function while holding the mainloop lock."""
-    def func_with_lock(*args, **kwargs):
-        self = args[0]
-        with self._lock_mainloop():
-            return func(*args[1:], **kwargs)
-    return func_with_lock
-
-
-def _lock_and_block(func):
-    """Call a pulseaudio function while holding the mainloop lock, and
-       block until the operation has finished.
-
-    Use this for pulseaudio functions that return a `pa_operation *`.
-
-    """
-    def func_with_lock(*args, **kwargs):
-        self = args[0]
-        with self._lock_mainloop():
-            operation = func(*args[1:], **kwargs)
-        self._block_operation(operation)
-        self._pa_operation_unref(operation)
-    return func_with_lock
-
-
-class _PulseAudio:
-    """Context manager for communcation with Pulseaudio.
-
-    This instantiates the pulseaudio main loop, and a pulseaudio
-    context. Together, these provide the building blocks for
-    interacting with pulseaudio.
-
-    Pulseaudio can be interacted with as soon as the context manager
-    is entered.
-
-    This can be used to query the pulseaudio server for sources,
-    sinks, and server information, and provides thread-safe access to
-    the main pulseaudio functions.
-
-    Any function that would return a `pa_operation *` in pulseaudio
-    will block until the operation has finished.
-
-    This context manager can only be entered once, and can not be used
-    after it is closed.
-
-    """
-
-    def __init__(self):
-        # these functions are called before the mainloop starts, so we
-        # don't need to hold the lock:
-        self.mainloop = _pa.pa_threaded_mainloop_new()
-        self.mainloop_api = _pa.pa_threaded_mainloop_get_api(self.mainloop)
-        self.context = _pa.pa_context_new(self.mainloop_api, b"audio")
-        _pa.pa_context_connect(self.context, _ffi.NULL, 
_pa.PA_CONTEXT_NOFLAGS, _ffi.NULL)
-
-    def __enter__(self):
-        _pa.pa_threaded_mainloop_start(self.mainloop)
-        # from now on, all pulseaudio interactions needs to hold the
-        # mainloop lock.
-        while self._pa_context_get_state(self.context) != _pa.PA_CONTEXT_READY:
-            time.sleep(0.001)
-        return self
-
-    def __exit__(self, exc_type, exc_value, traceback):
-        operation = self._pa_context_drain(self.context, _ffi.NULL, _ffi.NULL)
-        self._block_operation(operation)
-        self._pa_context_disconnect(self.context)
-        self._pa_context_unref(self.context)
-        # no more mainloop locking necessary from here on:
-        _pa.pa_threaded_mainloop_stop(self.mainloop)
-        _pa.pa_threaded_mainloop_free(self.mainloop)
-
-    def _block_operation(self, operation):
-        """Wait until the operation has finished."""
-        if operation == _ffi.NULL:
-            return
-        while self._pa_operation_get_state(operation) == 
_pa.PA_OPERATION_RUNNING:
-            time.sleep(0.001)
-
-    @property
-    def source_list(self):
-        """Return a list of dicts of information about available sources."""
-        info = []
-        @_ffi.callback("pa_source_info_cb_t")
-        def callback(context, source_info, eol, userdata):
-            if not eol:
-                
info.append(dict(name=_ffi.string(source_info.description).decode('utf-8'),
-                                 
id=_ffi.string(source_info.name).decode('utf-8')))
-        self._pa_context_get_source_info_list(self.context, callback, 
_ffi.NULL)
-        return info
-
-    def source_info(self, id):
-        """Return a dictionary of information about a specific source."""
-        info = []
-        @_ffi.callback("pa_source_info_cb_t")
-        def callback(context, source_info, eol, userdata):
-            if not eol:
-                info_dict = dict(latency=source_info.latency,
-                                 
configured_latency=source_info.configured_latency,
-                                 channels=source_info.sample_spec.channels,
-                                 
name=_ffi.string(source_info.description).decode('utf-8'))
-                for prop in ['device.class', 'device.api', 'device.bus']:
-                    data = _pa.pa_proplist_gets(source_info.proplist, 
prop.encode())
-                    info_dict[prop] = _ffi.string(data).decode('utf-8') if 
data else None
-                info.append(info_dict)
-
-        self._pa_context_get_source_info_by_name(self.context, id.encode(), 
callback, _ffi.NULL)
-        return info[0]
-
-    @property
-    def sink_list(self):
-        """Return a list of dicts of information about available sinks."""
-        info = []
-        @_ffi.callback("pa_sink_info_cb_t")
-        def callback(context, sink_info, eol, userdata):
-            if not eol:
-                
info.append((dict(name=_ffi.string(sink_info.description).decode('utf-8'),
-                                  
id=_ffi.string(sink_info.name).decode('utf-8'))))
-        self._pa_context_get_sink_info_list(self.context, callback, _ffi.NULL)
-        return info
-
-    def sink_info(self, id):
-        """Return a dictionary of information about a specific sink."""
-        info = []
-        @_ffi.callback("pa_sink_info_cb_t")
-        def callback(context, sink_info, eol, userdata):
-            if not eol:
-                info_dict = dict(latency=sink_info.latency,
-                                 
configured_latency=sink_info.configured_latency,
-                                 channels=sink_info.sample_spec.channels,
-                                 
name=_ffi.string(sink_info.description).decode('utf-8'))
-                for prop in ['device.class', 'device.api', 'device.bus']:
-                    data = _pa.pa_proplist_gets(sink_info.proplist, 
prop.encode())
-                    info_dict[prop] = _ffi.string(data).decode('utf-8') if 
data else None
-                info.append(info_dict)
-        self._pa_context_get_sink_info_by_name(self.context, id.encode(), 
callback, _ffi.NULL)
-        return info[0]
-
-    @property
-    def server_info(self):
-        """Return a dictionary of information about the server."""
-        info = {}
-        @_ffi.callback("pa_server_info_cb_t")
-        def callback(context, server_info, userdata):
-            info['server version'] = 
_ffi.string(server_info.server_version).decode('utf-8')
-            info['server name'] = 
_ffi.string(server_info.server_name).decode('utf-8')
-            info['default sink id'] = 
_ffi.string(server_info.default_sink_name).decode('utf-8')
-            info['default source id'] = 
_ffi.string(server_info.default_source_name).decode('utf-8')
-        self._pa_context_get_server_info(self.context, callback, _ffi.NULL)
-        return info
-
-    def _lock_mainloop(self):
-        """Context manager for locking the mainloop.
-
-        Hold this lock before calling any pulseaudio function while
-        the mainloop is running.
-
-        """
-
-        class Lock():
-            def __enter__(self_):
-                _pa.pa_threaded_mainloop_lock(self.mainloop)
-            def __exit__(self_, exc_type, exc_value, traceback):
-                _pa.pa_threaded_mainloop_unlock(self.mainloop)
-        return Lock()
-
-    # create thread-safe versions of all used pulseaudio functions:
-    _pa_context_get_source_info_list = 
_lock_and_block(_pa.pa_context_get_source_info_list)
-    _pa_context_get_source_info_by_name = 
_lock_and_block(_pa.pa_context_get_source_info_by_name)
-    _pa_context_get_sink_info_list = 
_lock_and_block(_pa.pa_context_get_sink_info_list)
-    _pa_context_get_sink_info_by_name = 
_lock_and_block(_pa.pa_context_get_sink_info_by_name)
-    _pa_context_get_server_info = 
_lock_and_block(_pa.pa_context_get_server_info)
-    _pa_context_get_state = _lock(_pa.pa_context_get_state)
-    _pa_context_drain = _lock(_pa.pa_context_drain)
-    _pa_context_disconnect = _lock(_pa.pa_context_disconnect)
-    _pa_context_unref = _lock(_pa.pa_context_unref)
-    _pa_operation_get_state = _lock(_pa.pa_operation_get_state)
-    _pa_operation_unref = _lock(_pa.pa_operation_unref)
-    _pa_stream_get_state = _lock(_pa.pa_stream_get_state)
-    _pa_sample_spec_valid = _lock(_pa.pa_sample_spec_valid)
-    _pa_stream_new = _lock(_pa.pa_stream_new)
-    _pa_stream_get_channel_map = _lock(_pa.pa_stream_get_channel_map)
-    _pa_stream_drain = _lock_and_block(_pa.pa_stream_drain)
-    _pa_stream_disconnect = _lock(_pa.pa_stream_disconnect)
-    _pa_stream_unref = _lock(_pa.pa_stream_unref)
-    _pa_stream_connect_record = _lock(_pa.pa_stream_connect_record)
-    _pa_stream_readable_size = _lock(_pa.pa_stream_readable_size)
-    _pa_stream_peek = _lock(_pa.pa_stream_peek)
-    _pa_stream_drop = _lock(_pa.pa_stream_drop)
-    _pa_stream_connect_playback = _lock(_pa.pa_stream_connect_playback)
-    _pa_stream_update_timing_info = 
_lock_and_block(_pa.pa_stream_update_timing_info)
-    _pa_stream_get_latency = _lock(_pa.pa_stream_get_latency)
-    _pa_stream_get_buffer_attr = _lock(_pa.pa_stream_get_buffer_attr)
-    _pa_stream_writable_size = _lock(_pa.pa_stream_writable_size)
-    _pa_stream_write = _lock(_pa.pa_stream_write)
-    _pa_stream_set_read_callback = _pa.pa_stream_set_read_callback


Reply via email to