Skip to content

Commit 1e16a3a

Browse files
committed
Add extra_settings and AsioSettings
See #4.
1 parent 24d0d98 commit 1e16a3a

File tree

2 files changed

+121
-34
lines changed

2 files changed

+121
-34
lines changed

doc/index.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ API Documentation
2222
:exclude-members: RawInputStream, RawOutputStream, RawStream,
2323
InputStream, OutputStream, Stream,
2424
CallbackFlags, CallbackStop, CallbackAbort,
25-
PortAudioError, DeviceList
25+
PortAudioError, DeviceList, AsioSettings
2626

2727
.. autoclass:: Stream
2828
:members:
@@ -51,6 +51,8 @@ API Documentation
5151

5252
.. autoclass:: PortAudioError
5353

54+
.. autoclass:: AsioSettings
55+
5456
.. only:: html
5557

5658
Index

sounddevice.py

+118-33
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,19 @@
224224
/* not implemented: Pa_GetStreamHostApiType */
225225
PaError Pa_GetSampleSize( PaSampleFormat format );
226226
void Pa_Sleep( long msec );
227+
228+
/* pa_asio.h */
229+
230+
#define paAsioUseChannelSelectors 0x01
231+
232+
typedef struct PaAsioStreamInfo
233+
{
234+
unsigned long size;
235+
PaHostApiTypeId hostApiType;
236+
unsigned long version;
237+
unsigned long flags;
238+
int *channelSelectors;
239+
} PaAsioStreamInfo;
227240
""")
228241

229242
try:
@@ -754,7 +767,7 @@ class _StreamBase(object):
754767
"""Base class for Raw{Input,Output}Stream."""
755768

756769
def __init__(self, kind, samplerate, blocksize, device, channels, dtype,
757-
latency, callback_wrapper, finished_callback,
770+
latency, extra_settings, callback_wrapper, finished_callback,
758771
clip_off, dither_off, never_drop_input,
759772
prime_output_buffers_using_stream_callback):
760773
if blocksize is None:
@@ -784,10 +797,13 @@ def __init__(self, kind, samplerate, blocksize, device, channels, dtype,
784797
ichannels, ochannels = _split(channels)
785798
idtype, odtype = _split(dtype)
786799
ilatency, olatency = _split(latency)
800+
iextra, oextra = _split(extra_settings)
787801
iparameters, idtype, isize, isamplerate = _get_stream_parameters(
788-
'input', idevice, ichannels, idtype, ilatency, samplerate)
802+
'input', idevice, ichannels, idtype, ilatency, iextra,
803+
samplerate)
789804
oparameters, odtype, osize, osamplerate = _get_stream_parameters(
790-
'output', odevice, ochannels, odtype, olatency, samplerate)
805+
'output', odevice, ochannels, odtype, olatency, oextra,
806+
samplerate)
791807
self._dtype = idtype, odtype
792808
self._device = iparameters.device, oparameters.device
793809
self._channels = iparameters.channelCount, oparameters.channelCount
@@ -799,8 +815,8 @@ def __init__(self, kind, samplerate, blocksize, device, channels, dtype,
799815
samplerate = isamplerate
800816
else:
801817
parameters, self._dtype, self._samplesize, samplerate = \
802-
_get_stream_parameters(
803-
kind, device, channels, dtype, latency, samplerate)
818+
_get_stream_parameters(kind, device, channels, dtype, latency,
819+
extra_settings, samplerate)
804820
self._device = parameters.device
805821
self._channels = parameters.channelCount
806822

@@ -1072,7 +1088,7 @@ class RawInputStream(_StreamBase):
10721088

10731089
def __init__(self, samplerate=None, blocksize=None,
10741090
device=None, channels=None, dtype=None, latency=None,
1075-
callback=None, finished_callback=None,
1091+
extra_settings=None, callback=None, finished_callback=None,
10761092
clip_off=None, dither_off=None, never_drop_input=None,
10771093
prime_output_buffers_using_stream_callback=None):
10781094
"""Open a "raw" input stream.
@@ -1109,8 +1125,8 @@ def callback_wrapper(iptr, optr, frames, time, status, _):
11091125

11101126
_StreamBase.__init__(
11111127
self, 'input', samplerate, blocksize, device, channels, dtype,
1112-
latency, callback and callback_wrapper, finished_callback,
1113-
clip_off, dither_off, never_drop_input,
1128+
latency, extra_settings, callback and callback_wrapper,
1129+
finished_callback, clip_off, dither_off, never_drop_input,
11141130
prime_output_buffers_using_stream_callback)
11151131

11161132
@property
@@ -1164,7 +1180,7 @@ class RawOutputStream(_StreamBase):
11641180

11651181
def __init__(self, samplerate=None, blocksize=None,
11661182
device=None, channels=None, dtype=None, latency=None,
1167-
callback=None, finished_callback=None,
1183+
extra_settings=None, callback=None, finished_callback=None,
11681184
clip_off=None, dither_off=None, never_drop_input=None,
11691185
prime_output_buffers_using_stream_callback=None):
11701186
"""Open a "raw" output stream.
@@ -1201,8 +1217,8 @@ def callback_wrapper(iptr, optr, frames, time, status, _):
12011217

12021218
_StreamBase.__init__(
12031219
self, 'output', samplerate, blocksize, device, channels, dtype,
1204-
latency, callback and callback_wrapper, finished_callback,
1205-
clip_off, dither_off, never_drop_input,
1220+
latency, extra_settings, callback and callback_wrapper,
1221+
finished_callback, clip_off, dither_off, never_drop_input,
12061222
prime_output_buffers_using_stream_callback)
12071223

12081224
@property
@@ -1267,7 +1283,7 @@ class RawStream(RawInputStream, RawOutputStream):
12671283

12681284
def __init__(self, samplerate=None, blocksize=None,
12691285
device=None, channels=None, dtype=None, latency=None,
1270-
callback=None, finished_callback=None,
1286+
extra_settings=None, callback=None, finished_callback=None,
12711287
clip_off=None, dither_off=None, never_drop_input=None,
12721288
prime_output_buffers_using_stream_callback=None):
12731289
"""Open a "raw" input/output stream.
@@ -1320,8 +1336,8 @@ def callback_wrapper(iptr, optr, frames, time, status, _):
13201336

13211337
_StreamBase.__init__(
13221338
self, 'duplex', samplerate, blocksize, device, channels, dtype,
1323-
latency, callback and callback_wrapper, finished_callback,
1324-
clip_off, dither_off, never_drop_input,
1339+
latency, extra_settings, callback and callback_wrapper,
1340+
finished_callback, clip_off, dither_off, never_drop_input,
13251341
prime_output_buffers_using_stream_callback)
13261342

13271343

@@ -1330,7 +1346,7 @@ class InputStream(RawInputStream):
13301346

13311347
def __init__(self, samplerate=None, blocksize=None,
13321348
device=None, channels=None, dtype=None, latency=None,
1333-
callback=None, finished_callback=None,
1349+
extra_settings=None, callback=None, finished_callback=None,
13341350
clip_off=None, dither_off=None, never_drop_input=None,
13351351
prime_output_buffers_using_stream_callback=None):
13361352
"""Open an input stream.
@@ -1366,8 +1382,8 @@ def callback_wrapper(iptr, optr, frames, time, status, _):
13661382

13671383
_StreamBase.__init__(
13681384
self, 'input', samplerate, blocksize, device, channels, dtype,
1369-
latency, callback and callback_wrapper, finished_callback,
1370-
clip_off, dither_off, never_drop_input,
1385+
latency, extra_settings, callback and callback_wrapper,
1386+
finished_callback, clip_off, dither_off, never_drop_input,
13711387
prime_output_buffers_using_stream_callback)
13721388

13731389
def read(self, frames):
@@ -1412,7 +1428,7 @@ class OutputStream(RawOutputStream):
14121428

14131429
def __init__(self, samplerate=None, blocksize=None,
14141430
device=None, channels=None, dtype=None, latency=None,
1415-
callback=None, finished_callback=None,
1431+
extra_settings=None, callback=None, finished_callback=None,
14161432
clip_off=None, dither_off=None, never_drop_input=None,
14171433
prime_output_buffers_using_stream_callback=None):
14181434
"""Open an output stream.
@@ -1448,8 +1464,8 @@ def callback_wrapper(iptr, optr, frames, time, status, _):
14481464

14491465
_StreamBase.__init__(
14501466
self, 'output', samplerate, blocksize, device, channels, dtype,
1451-
latency, callback and callback_wrapper, finished_callback,
1452-
clip_off, dither_off, never_drop_input,
1467+
latency, extra_settings, callback and callback_wrapper,
1468+
finished_callback, clip_off, dither_off, never_drop_input,
14531469
prime_output_buffers_using_stream_callback)
14541470

14551471
def write(self, data):
@@ -1503,7 +1519,7 @@ class Stream(InputStream, OutputStream):
15031519

15041520
def __init__(self, samplerate=None, blocksize=None,
15051521
device=None, channels=None, dtype=None, latency=None,
1506-
callback=None, finished_callback=None,
1522+
extra_settings=None, callback=None, finished_callback=None,
15071523
clip_off=None, dither_off=None, never_drop_input=None,
15081524
prime_output_buffers_using_stream_callback=None):
15091525
"""Open a stream for input and output.
@@ -1602,6 +1618,9 @@ def __init__(self, samplerate=None, blocksize=None,
16021618
next practical value -- i.e. to provide an equal or higher
16031619
latency wherever possible. Actual latency values for an
16041620
open stream may be retrieved using the `latency` attribute.
1621+
extra_settings : settings object or pair thereof, optional
1622+
This can be used for host-API-specific input/output
1623+
settings. See `default.extra_settings`.
16051624
callback : callable, optional
16061625
User-supplied function to consume, process or generate audio
16071626
data in response to requests from an `active` stream.
@@ -1747,8 +1766,8 @@ def callback_wrapper(iptr, optr, frames, time, status, _):
17471766

17481767
_StreamBase.__init__(
17491768
self, 'duplex', samplerate, blocksize, device, channels, dtype,
1750-
latency, callback and callback_wrapper, finished_callback,
1751-
clip_off, dither_off, never_drop_input,
1769+
latency, extra_settings, callback and callback_wrapper,
1770+
finished_callback, clip_off, dither_off, never_drop_input,
17521771
prime_output_buffers_using_stream_callback)
17531772

17541773

@@ -1929,12 +1948,12 @@ def __repr__(self):
19291948
class default(object):
19301949
"""Get/set defaults for the *sounddevice* module.
19311950
1932-
The attributes `device`, `channels`, `dtype` and `latency` accept
1933-
single values which specify the given property for both input and
1934-
output. However, if the property differs between input and output,
1935-
pairs of values can be used, where the first value specifies the
1936-
input and the second value specifies the output.
1937-
All other attributes are always single values.
1951+
The attributes `device`, `channels`, `dtype`, `latency` and
1952+
`extra_settings` accept single values which specify the given
1953+
property for both input and output. However, if the property
1954+
differs between input and output, pairs of values can be used, where
1955+
the first value specifies the input and the second value specifies
1956+
the output. All other attributes are always single values.
19381957
19391958
Examples
19401959
--------
@@ -2025,6 +2044,15 @@ class default(object):
20252044
20262045
"""
20272046

2047+
extra_settings = _default_extra_settings = None, None
2048+
"""Host-API-specific input/output settings.
2049+
2050+
See Also
2051+
--------
2052+
AsioSettings
2053+
2054+
"""
2055+
20282056
samplerate = None
20292057
"""Sampling frequency in Hertz (= frames per second).
20302058
@@ -2076,6 +2104,8 @@ def __init__(self):
20762104
vars(self)['channels'] = _InputOutputPair(self, '_default_channels')
20772105
vars(self)['dtype'] = _InputOutputPair(self, '_default_dtype')
20782106
vars(self)['latency'] = _InputOutputPair(self, '_default_latency')
2107+
vars(self)['extra_settings'] = _InputOutputPair(self,
2108+
'_default_extra_settings')
20792109

20802110
def __setattr__(self, name, value):
20812111
"""Only allow setting existing attributes."""
@@ -2137,6 +2167,58 @@ class CallbackAbort(Exception):
21372167
"""
21382168

21392169

2170+
class AsioSettings(object):
2171+
2172+
def __init__(self, channel_selectors):
2173+
"""ASIO-specific input/output settings.
2174+
2175+
Objects of this class can be used as *extra_settings* argument
2176+
to `Stream()` (and variants) or as `default.extra_settings`.
2177+
2178+
Parameters
2179+
----------
2180+
channel_selectors : list of int
2181+
Support for opening only specific channels of an ASIO
2182+
device. *channel_selectors* is a list of integers
2183+
specifying the (zero-based) channel numbers to use.
2184+
The length of *channel_selectors* must match the
2185+
corresponding *channels* parameter of `Stream()` (or
2186+
variants), otherwise a crash may result.
2187+
The values in the selectors array must specify channels
2188+
within the range of supported channels.
2189+
2190+
Examples
2191+
--------
2192+
Setting output channels when calling `play()`:
2193+
2194+
>>> import sounddevice as sd
2195+
>>> asio_out = sd.AsioSettings(channel_selectors=[12, 13])
2196+
>>> sd.play(..., extra_settings=asio_out)
2197+
2198+
Setting default output channels:
2199+
2200+
>>> sd.default.extra_settings = asio_out
2201+
>>> sd.play(...)
2202+
2203+
Setting input channels as well:
2204+
2205+
>>> asio_in = sd.AsioSettings(channel_selectors=[8])
2206+
>>> sd.default.extra_settings = asio_in, asio_out
2207+
>>> sd.playrec(..., channels=1, ...)
2208+
2209+
"""
2210+
if isinstance(channel_selectors, int):
2211+
raise TypeError('channel_selectors must be a list or tuple')
2212+
# int array must be kept alive!
2213+
self._selectors = _ffi.new('int[]', channel_selectors)
2214+
self._streaminfo = _ffi.new('PaAsioStreamInfo*', dict(
2215+
size=_ffi.sizeof('PaAsioStreamInfo'),
2216+
hostApiType=_lib.paASIO,
2217+
version=1,
2218+
flags=_lib.paAsioUseChannelSelectors,
2219+
channelSelectors=self._selectors))
2220+
2221+
21402222
class _CallbackContext(object):
21412223
"""Helper class for re-use in play()/rec()/playrec() callbacks."""
21422224

@@ -2317,7 +2399,8 @@ def _check_dtype(dtype):
23172399
return dtype
23182400

23192401

2320-
def _get_stream_parameters(kind, device, channels, dtype, latency, samplerate):
2402+
def _get_stream_parameters(kind, device, channels, dtype, latency,
2403+
extra_settings, samplerate):
23212404
"""Get parameters for one direction (input or output) of a stream."""
23222405
if device is None:
23232406
device = default.device[kind]
@@ -2327,6 +2410,8 @@ def _get_stream_parameters(kind, device, channels, dtype, latency, samplerate):
23272410
dtype = default.dtype[kind]
23282411
if latency is None:
23292412
latency = default.latency[kind]
2413+
if extra_settings is None:
2414+
extra_settings = default.extra_settings[kind]
23302415
if samplerate is None:
23312416
samplerate = default.samplerate
23322417

@@ -2348,9 +2433,9 @@ def _get_stream_parameters(kind, device, channels, dtype, latency, samplerate):
23482433
latency = info['default_' + latency + '_' + kind + '_latency']
23492434
if samplerate is None:
23502435
samplerate = info['default_samplerate']
2351-
parameters = _ffi.new(
2352-
'PaStreamParameters*',
2353-
(device, channels, sampleformat, latency, _ffi.NULL))
2436+
parameters = _ffi.new('PaStreamParameters*', (
2437+
device, channels, sampleformat, latency,
2438+
extra_settings._streaminfo if extra_settings else _ffi.NULL))
23542439
return parameters, dtype, samplesize, samplerate
23552440

23562441

0 commit comments

Comments
 (0)