Skip to content
This repository was archived by the owner on May 6, 2022. It is now read-only.

Commit e9b132c

Browse files
authored
Add sounddevice as an audio input source. (#75)
This change introduces `SoundDeviceInput` to the `io` module. Sounddevice is now the preferred method of capturing audio input, due to better multi-platform distribution support than PyAudio. However, PyAudio will continue to be supported for the forseeable future.
1 parent 1bd4e4e commit e9b132c

File tree

7 files changed

+317
-82
lines changed

7 files changed

+317
-82
lines changed

docs/source/spokestack.io.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@ spokestack.io.pyaudio
66

77
.. automodule:: spokestack.io.pyaudio
88
:members:
9+
10+
spokestack.io.sound_device
11+
----------------------------
12+
.. automodule:: spokestack.io.sound_device
13+
:members:

mypy.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ ignore_missing_imports = True
3131
ignore_missing_imports = True
3232
[mypy-Cython.*]
3333
ignore_missing_imports = True
34+
[mypy-sounddevice]
35+
ignore_missing_imports = True

requirements.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ requests
3434
streamp3
3535
google-cloud-speech
3636
cython
37+
sounddevice

requirements.txt

Lines changed: 209 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2,84 +2,216 @@
22
# This file is autogenerated by pip-compile
33
# To update, run:
44
#
5-
# pip-compile
5+
# pip-compile requirements.in
66
#
7-
appdirs==1.4.4 # via black, virtualenv
8-
attrs==19.3.0 # via black, flake8-mypy, pytest
9-
black==19.10b0 # via -r requirements.in
10-
cachetools==4.1.1 # via google-auth
11-
certifi==2020.6.20 # via requests
12-
cfgv==3.1.0 # via pre-commit
13-
chardet==3.0.4 # via requests
14-
click==7.1.2 # via black, pip-tools
15-
colorama==0.4.3 # via pytest-watch
16-
coverage==5.2 # via coveralls, pytest-cov
17-
coveralls==2.1.1 # via -r requirements.in
18-
cython==0.29.21 # via -r requirements.in, streamp3
19-
distlib==0.3.1 # via virtualenv
20-
docopt==0.6.2 # via coveralls, pytest-watch
21-
filelock==3.0.12 # via virtualenv
22-
flake8==3.8.3 # via -r requirements.in, flake8-mypy
23-
flake8-mypy==17.8.0 # via -r requirements.in
24-
gevent==20.6.2 # via websocket
25-
google-api-core[grpc]==1.23.0 # via google-cloud-speech
26-
google-auth==1.22.1 # via google-api-core
27-
google-cloud-speech==2.0.0 # via -r requirements.in
28-
googleapis-common-protos==1.52.0 # via google-api-core
29-
greenlet==0.4.16 # via gevent, websocket
30-
grpcio==1.33.1 # via google-api-core
31-
identify==1.4.25 # via pre-commit
32-
idna==2.10 # via requests
33-
isort==4.3.21 # via -r requirements.in
34-
libcst==0.3.13 # via google-cloud-speech
35-
mccabe==0.6.1 # via flake8
36-
more-itertools==8.4.0 # via pytest
37-
mypy==0.782 # via -r requirements.in, flake8-mypy
38-
mypy-extensions==0.4.3 # via mypy, typing-inspect
39-
nodeenv==1.4.0 # via pre-commit
40-
numpy==1.19.1 # via -r requirements.in
41-
packaging==20.4 # via pytest
42-
pathspec==0.8.0 # via black
43-
pathtools==0.1.2 # via watchdog
44-
pip-tools==5.2.1 # via -r requirements.in
45-
pluggy==0.13.1 # via pytest
46-
pre-commit==2.6.0 # via -r requirements.in
47-
proto-plus==1.11.0 # via google-cloud-speech
48-
protobuf==3.13.0 # via google-api-core, googleapis-common-protos, proto-plus
49-
py==1.9.0 # via pytest
50-
py-cpuinfo==7.0.0 # via pytest-benchmark
51-
pyasn1==0.4.8 # via pyasn1-modules, rsa
52-
pyasn1-modules==0.2.8 # via google-auth
53-
pyaudio==0.2.11 # via -r requirements.in
54-
pycodestyle==2.6.0 # via flake8
55-
pyfakefs==4.1.0 # via -r requirements.in
56-
pyflakes==2.2.0 # via flake8
57-
pyparsing==2.4.7 # via packaging
58-
pytest==5.4.3 # via -r requirements.in, pytest-benchmark, pytest-cov, pytest-mock, pytest-watch
59-
pytest-benchmark==3.2.3 # via -r requirements.in
60-
pytest-cov==2.10.0 # via -r requirements.in
61-
pytest-mock==3.2.0 # via -r requirements.in
62-
pytest-watch==4.2.0 # via -r requirements.in
63-
pytz==2020.1 # via google-api-core
64-
pyyaml==5.3.1 # via libcst, pre-commit
65-
regex==2020.7.14 # via black
66-
requests==2.24.0 # via -r requirements.in, coveralls, google-api-core
67-
rsa==4.6 # via google-auth
68-
six==1.15.0 # via google-api-core, google-auth, grpcio, packaging, pip-tools, protobuf, virtualenv
69-
streamp3==0.1.7 # via -r requirements.in
70-
tokenizers==0.8.1 # via -r requirements.in
71-
toml==0.10.1 # via black, pre-commit
72-
typed-ast==1.4.1 # via black, mypy
73-
typing-extensions==3.7.4.2 # via libcst, mypy, typing-inspect
74-
typing-inspect==0.6.0 # via libcst
75-
urllib3==1.25.10 # via requests
76-
virtualenv==20.0.27 # via pre-commit
77-
watchdog==0.10.3 # via pytest-watch
78-
wcwidth==0.2.5 # via pytest
79-
websocket==0.2.1 # via -r requirements.in
80-
wheel==0.34.2 # via -r requirements.in
81-
zope.event==4.4 # via gevent
82-
zope.interface==5.1.0 # via gevent
7+
appdirs==1.4.4
8+
# via
9+
# black
10+
# virtualenv
11+
attrs==19.3.0
12+
# via
13+
# black
14+
# flake8-mypy
15+
# pytest
16+
black==19.10b0
17+
# via -r requirements.in
18+
cachetools==4.1.1
19+
# via google-auth
20+
certifi==2020.6.20
21+
# via requests
22+
cffi==1.14.5
23+
# via sounddevice
24+
cfgv==3.1.0
25+
# via pre-commit
26+
chardet==3.0.4
27+
# via requests
28+
click==7.1.2
29+
# via
30+
# black
31+
# pip-tools
32+
colorama==0.4.3
33+
# via pytest-watch
34+
coverage==5.2
35+
# via
36+
# coveralls
37+
# pytest-cov
38+
coveralls==2.1.1
39+
# via -r requirements.in
40+
cython==0.29.21
41+
# via
42+
# -r requirements.in
43+
# streamp3
44+
distlib==0.3.1
45+
# via virtualenv
46+
docopt==0.6.2
47+
# via
48+
# coveralls
49+
# pytest-watch
50+
filelock==3.0.12
51+
# via -r requirements.in
52+
flake8==3.8.3
53+
# via virtualenv
54+
flake8-mypy==17.8.0
55+
# via
56+
# -r requirements.in
57+
# flake8-mypy
58+
gevent==20.6.2
59+
# via websocket
60+
google-api-core[grpc]==1.23.0
61+
# via google-cloud-speech
62+
google-auth==1.22.1
63+
# via google-api-core
64+
google-cloud-speech==2.0.0
65+
# via -r requirements.in
66+
googleapis-common-protos==1.52.0
67+
# via google-api-core
68+
greenlet==0.4.16
69+
# via
70+
# gevent
71+
# websocket
72+
grpcio==1.33.1
73+
# via google-api-core
74+
identify==1.4.25
75+
# via pre-commit
76+
idna==2.10
77+
# via requests
78+
isort==4.3.21
79+
# via -r requirements.in
80+
libcst==0.3.13
81+
# via google-cloud-speech
82+
mccabe==0.6.1
83+
# via flake8
84+
more-itertools==8.4.0
85+
# via
86+
# mypy
87+
# typing-inspect
88+
mypy==0.782
89+
# via pytest
90+
mypy-extensions==0.4.3
91+
# via
92+
# -r requirements.in
93+
# flake8-mypy
94+
nodeenv==1.4.0
95+
# via pre-commit
96+
numpy==1.19.1
97+
# via -r requirements.in
98+
packaging==20.4
99+
# via pytest
100+
pathspec==0.8.0
101+
# via black
102+
pathtools==0.1.2
103+
# via watchdog
104+
pip-tools==5.2.1
105+
# via -r requirements.in
106+
pluggy==0.13.1
107+
# via pytest
108+
pre-commit==2.6.0
109+
# via -r requirements.in
110+
proto-plus==1.11.0
111+
# via google-cloud-speech
112+
protobuf==3.13.0
113+
# via pytest-benchmark
114+
py==1.9.0
115+
# via
116+
# google-api-core
117+
# googleapis-common-protos
118+
# proto-plus
119+
py-cpuinfo==7.0.0
120+
# via google-auth
121+
pyasn1==0.4.8
122+
# via pytest
123+
pyasn1-modules==0.2.8
124+
# via
125+
# pyasn1-modules
126+
# rsa
127+
pyaudio==0.2.11
128+
# via -r requirements.in
129+
pycodestyle==2.6.0
130+
# via flake8
131+
pycparser==2.20
132+
# via cffi
133+
pyfakefs==4.1.0
134+
# via -r requirements.in
135+
pyflakes==2.2.0
136+
# via flake8
137+
pyparsing==2.4.7
138+
# via -r requirements.in
139+
pytest==5.4.3
140+
# via packaging
141+
pytest-benchmark==3.2.3
142+
# via -r requirements.in
143+
pytest-cov==2.10.0
144+
# via -r requirements.in
145+
pytest-mock==3.2.0
146+
# via -r requirements.in
147+
pytest-watch==4.2.0
148+
# via
149+
# -r requirements.in
150+
# pytest-benchmark
151+
# pytest-cov
152+
# pytest-mock
153+
# pytest-watch
154+
pytz==2020.1
155+
# via google-api-core
156+
pyyaml==5.3.1
157+
# via
158+
# libcst
159+
# pre-commit
160+
regex==2020.7.14
161+
# via black
162+
requests==2.24.0
163+
# via
164+
# -r requirements.in
165+
# coveralls
166+
# google-api-core
167+
rsa==4.6
168+
# via google-auth
169+
six==1.15.0
170+
# via
171+
# google-api-core
172+
# google-auth
173+
# grpcio
174+
# packaging
175+
# pip-tools
176+
# protobuf
177+
# virtualenv
178+
sounddevice==0.4.1
179+
# via -r requirements.in
180+
streamp3==0.1.7
181+
# via -r requirements.in
182+
tokenizers==0.8.1
183+
# via -r requirements.in
184+
toml==0.10.1
185+
# via
186+
# black
187+
# pre-commit
188+
typed-ast==1.4.1
189+
# via
190+
# black
191+
# mypy
192+
typing-extensions==3.7.4.2
193+
# via
194+
# libcst
195+
# mypy
196+
# typing-inspect
197+
typing-inspect==0.6.0
198+
# via libcst
199+
urllib3==1.25.10
200+
# via requests
201+
virtualenv==20.0.27
202+
# via pre-commit
203+
watchdog==0.10.3
204+
# via pytest-watch
205+
wcwidth==0.2.5
206+
# via pytest
207+
websocket==0.2.1
208+
# via -r requirements.in
209+
wheel==0.34.2
210+
# via -r requirements.in
211+
zope.event==4.4
212+
# via gevent
213+
zope.interface==5.1.0
214+
# via gevent
83215

84216
# The following packages are considered to be unsafe in a requirements file:
85217
# pip

spokestack/io/sound_device.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""Audio input component based on the sounddevice library."""
2+
from typing import Any
3+
4+
import numpy as np
5+
import sounddevice as sd
6+
7+
8+
class SoundDeviceInput:
9+
"""
10+
Audio input source using sounddevice.
11+
12+
Parameters
13+
----------
14+
sample_rate (Hz): int, optional
15+
audio sample rate, by default 16000
16+
frame_width (ms): int, optional
17+
size of the audio frame, by default 20
18+
"""
19+
20+
def __init__(
21+
self, sample_rate: int = 16000, frame_width: int = 20, **kwargs: Any
22+
) -> None:
23+
24+
self._frame_size = int(sample_rate / 1000 * frame_width)
25+
self._sample_rate = sample_rate
26+
self._stream = sd.Stream(samplerate=sample_rate)
27+
28+
def read(self) -> np.array:
29+
"""
30+
Read audio from input source.
31+
32+
Returns
33+
-------
34+
np.array
35+
NumPy array of audio
36+
"""
37+
return self._stream.read(self._frame_size)
38+
39+
def start(self) -> None:
40+
"""Start the audio stream."""
41+
self._stream.start()
42+
43+
def stop(self) -> None:
44+
"""Stop the audio stream."""
45+
self._stream.stop()
46+
47+
def close(self) -> None:
48+
"""Close the audio stream."""
49+
self._stream.close()
50+
51+
@property
52+
def is_active(self) -> bool:
53+
"""
54+
Active status of the audio stream.
55+
56+
Returns
57+
-------
58+
bool
59+
True if active, False otherwise
60+
"""
61+
return self._stream.active
62+
63+
@property
64+
def is_stopped(self) -> bool:
65+
"""
66+
Stop status of the audio stream.
67+
68+
Returns
69+
-------
70+
bool
71+
True if stopped, False otherwise
72+
"""
73+
return self._stream.stopped

0 commit comments

Comments
 (0)