Skip to content

Commit b5e50e0

Browse files
committed
Allow device strings in query_devices() ...
... and allow to conveniently get information about the default input/output device.
1 parent 60d1ce3 commit b5e50e0

File tree

2 files changed

+54
-23
lines changed

2 files changed

+54
-23
lines changed

examples/spectrogram.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@
5151
print(sd.query_devices())
5252
parser.exit()
5353

54-
if args.device is None:
55-
args.device = sd.default.device['input']
56-
samplerate = sd.query_devices(args.device)['default_samplerate']
54+
samplerate = sd.query_devices(args.device, 'input')['default_samplerate']
5755

5856
delta_f = (high - low) / (args.columns - 1)
5957
fftsize = np.ceil(samplerate / delta_f).astype(int)

sounddevice.py

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -498,26 +498,31 @@ def get_status():
498498
raise RuntimeError("play()/rec()/playrec() was not called yet")
499499

500500

501-
def query_devices(index=None):
501+
def query_devices(device=None, kind=None):
502502
"""Return information about available devices.
503503
504504
Information and capabilities of PortAudio devices.
505505
Devices may support input, output or both input and output.
506506
507-
To find the default device, use :attr:`default.device`.
507+
To find the default input/output device, use :attr:`default.device`.
508508
509509
Parameters
510510
----------
511-
index : int, optional
512-
If specified, information about only the given device `index` is
511+
device : int or str, optional
512+
Numeric device ID or device name substring(s).
513+
If specified, information about only the given `device` is
513514
returned in a single dictionary.
515+
kind : {'input', 'output'}, optional
516+
If `device` is not specified and `kind` is ``'input'`` or
517+
``'output'``, a single dictionary is returned with information
518+
about the default input or output device, respectively.
514519
515520
Returns
516521
-------
517522
dict or DeviceList
518-
A dictionary with information about the given device `index` or
519-
-- if no `index` was specified -- a :class:`DeviceList`
520-
containing one dictionary for each available device.
523+
A dictionary with information about the given `device` or -- if
524+
no `device` was specified -- a :class:`DeviceList` containing
525+
one dictionary for each available device.
521526
The dictionaries have the following keys:
522527
523528
``'name'``
@@ -602,18 +607,21 @@ def query_devices(index=None):
602607
4 Built-in Digital Output, Core Audio (0 in, 2 out)
603608
604609
"""
605-
if index is None:
610+
if kind not in ('input', 'output', None):
611+
raise ValueError("Invalid kind: {0!r}".format(kind))
612+
if device is None and kind is None:
606613
return DeviceList(query_devices(i)
607614
for i in range(_check(_lib.Pa_GetDeviceCount())))
608-
info = _lib.Pa_GetDeviceInfo(index)
615+
device = _get_device_id(device, kind, raise_on_error=True)
616+
info = _lib.Pa_GetDeviceInfo(device)
609617
if not info:
610-
raise PortAudioError("Error querying device {0}".format(index))
618+
raise PortAudioError("Error querying device {0}".format(device))
611619
assert info.structVersion == 2
612620
if info.hostApi == _lib.Pa_HostApiTypeIdToHostApiIndex(_lib.paDirectSound):
613621
encoding = 'mbcs'
614622
else:
615623
encoding = 'utf-8'
616-
return {
624+
device_dict = {
617625
'name': _ffi.string(info.name).decode(encoding, 'replace'),
618626
'hostapi': info.hostApi,
619627
'max_input_channels': info.maxInputChannels,
@@ -624,6 +632,10 @@ def query_devices(index=None):
624632
'default_high_output_latency': info.defaultHighOutputLatency,
625633
'default_samplerate': info.defaultSampleRate,
626634
}
635+
if kind and device_dict['max_' + kind + '_channels'] < 1:
636+
raise ValueError(
637+
"Not an {0} device: {1!r}".format(kind, device_dict['name']))
638+
return device_dict
627639

628640

629641
def query_hostapis(index=None):
@@ -1778,7 +1790,7 @@ class DeviceList(tuple):
17781790

17791791
def __repr__(self):
17801792
idev, odev = [
1781-
dev if isinstance(dev, int) else _find_device_id(kind, dev)
1793+
dev if isinstance(dev, int) else _get_device_id(dev, kind)
17821794
for kind, dev in zip(('input', 'output'), default.device)
17831795
]
17841796
digits = len(str(_lib.Pa_GetDeviceCount() - 1))
@@ -2319,8 +2331,7 @@ def _get_stream_parameters(kind, device, channels, dtype, latency, samplerate):
23192331
if samplerate is None:
23202332
samplerate = default.samplerate
23212333

2322-
if not isinstance(device, int):
2323-
device = _find_device_id(kind, device, raise_on_error=True)
2334+
device = _get_device_id(device, kind, raise_on_error=True)
23242335
info = query_devices(device)
23252336
if channels is None:
23262337
channels = info['max_' + kind + '_channels']
@@ -2398,15 +2409,34 @@ def _check(err, msg=""):
23982409
return err
23992410

24002411

2401-
def _find_device_id(kind, query_string, raise_on_error=False):
2412+
def _get_device_id(id_or_query_string, kind, raise_on_error=False):
24022413
"""Return device ID given space-separated substrings."""
2414+
assert kind in ('input', 'output', None)
2415+
2416+
if id_or_query_string is None:
2417+
id_or_query_string = default.device
2418+
2419+
idev, odev = _split(id_or_query_string)
2420+
if kind == 'input':
2421+
id_or_query_string = idev
2422+
elif kind == 'output':
2423+
id_or_query_string = odev
2424+
else:
2425+
if idev == odev:
2426+
id_or_query_string = idev
2427+
else:
2428+
raise ValueError("Input and output device are different: {0!r}"
2429+
.format(id_or_query_string))
2430+
2431+
if isinstance(id_or_query_string, int):
2432+
return id_or_query_string
24032433
device_list = []
24042434
for id, info in enumerate(query_devices()):
2405-
if info['max_' + kind + '_channels'] > 0:
2435+
if not kind or info['max_' + kind + '_channels'] > 0:
24062436
hostapi_info = query_hostapis(info['hostapi'])
24072437
device_list.append((id, info['name'], hostapi_info['name']))
24082438

2409-
query_string = query_string.lower()
2439+
query_string = id_or_query_string.lower()
24102440
substrings = query_string.split()
24112441
matches = []
24122442
exact_device_matches = []
@@ -2423,19 +2453,22 @@ def _find_device_id(kind, query_string, raise_on_error=False):
24232453
if device_string.lower() == query_string:
24242454
exact_device_matches.append(id)
24252455

2456+
if kind is None:
2457+
kind = 'input/output' # Just used for error messages
2458+
24262459
if not matches:
24272460
if raise_on_error:
24282461
raise ValueError(
2429-
"No " + kind + " device matching " + repr(query_string))
2462+
"No " + kind + " device matching " + repr(id_or_query_string))
24302463
else:
24312464
return -1
24322465
if len(matches) > 1:
24332466
if len(exact_device_matches) == 1:
24342467
return exact_device_matches[0]
24352468
if raise_on_error:
24362469
raise ValueError("Multiple " + kind + " devices found for " +
2437-
repr(query_string) + ": " +
2438-
'; '.join('[{0}] {1}'.format(id, name)
2470+
repr(id_or_query_string) + ":\n" +
2471+
'\n'.join('[{0}] {1}'.format(id, name)
24392472
for id, name in matches))
24402473
else:
24412474
return -1

0 commit comments

Comments
 (0)