amods 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- amods-0.1.0/LICENSE +21 -0
- amods-0.1.0/PKG-INFO +351 -0
- amods-0.1.0/README.md +297 -0
- amods-0.1.0/pyproject.toml +55 -0
- amods-0.1.0/setup.cfg +4 -0
- amods-0.1.0/src/amods/__init__.py +7 -0
- amods-0.1.0/src/amods/audio.py +51 -0
- amods-0.1.0/src/amods/cli.py +55 -0
- amods-0.1.0/src/amods/config.py +94 -0
- amods-0.1.0/src/amods/configs/__init__.py +0 -0
- amods-0.1.0/src/amods/configs/concealer/default.yaml +13 -0
- amods-0.1.0/src/amods/configs/forecaster/default.yaml +1 -0
- amods-0.1.0/src/amods/configs/stream/48khz_50msbuff_dock.yaml +13 -0
- amods-0.1.0/src/amods/configs/stream/48khz_50msbuff_nodock.yaml +13 -0
- amods-0.1.0/src/amods/configs/stream/default.yaml +13 -0
- amods-0.1.0/src/amods/configs/vad/default_concealer.yaml +2 -0
- amods-0.1.0/src/amods/configs/vad/default_source.yaml +2 -0
- amods-0.1.0/src/amods/configs/vad/none_concealer.yaml +1 -0
- amods-0.1.0/src/amods/configs/vad/none_source.yaml +1 -0
- amods-0.1.0/src/amods/configs/vad/silero_concealer.yaml +2 -0
- amods-0.1.0/src/amods/configs/vad/silero_source.yaml +2 -0
- amods-0.1.0/src/amods/configs/vad/ten_concealer.yaml +2 -0
- amods-0.1.0/src/amods/configs/vad/ten_source.yaml +2 -0
- amods-0.1.0/src/amods/configs/vad/webrtc_concealer.yaml +3 -0
- amods-0.1.0/src/amods/configs/vad/webrtc_source.yaml +3 -0
- amods-0.1.0/src/amods/gui.py +468 -0
- amods-0.1.0/src/amods/latency.py +133 -0
- amods-0.1.0/src/amods/models/__init__.py +0 -0
- amods-0.1.0/src/amods/models/adversary.py +11 -0
- amods-0.1.0/src/amods/models/concealer/__init__.py +14 -0
- amods-0.1.0/src/amods/models/concealer/base.py +346 -0
- amods-0.1.0/src/amods/models/concealer/granspeechmask.py +218 -0
- amods-0.1.0/src/amods/models/denoiser.py +28 -0
- amods-0.1.0/src/amods/models/forecaster.py +17 -0
- amods-0.1.0/src/amods/models/vad.py +245 -0
- amods-0.1.0/src/amods/stream.py +403 -0
- amods-0.1.0/src/amods.egg-info/PKG-INFO +351 -0
- amods-0.1.0/src/amods.egg-info/SOURCES.txt +50 -0
- amods-0.1.0/src/amods.egg-info/dependency_links.txt +1 -0
- amods-0.1.0/src/amods.egg-info/entry_points.txt +4 -0
- amods-0.1.0/src/amods.egg-info/requires.txt +17 -0
- amods-0.1.0/src/amods.egg-info/top_level.txt +1 -0
- amods-0.1.0/tests/test_audio.py +58 -0
- amods-0.1.0/tests/test_cli.py +69 -0
- amods-0.1.0/tests/test_config.py +84 -0
- amods-0.1.0/tests/test_models_adversary.py +28 -0
- amods-0.1.0/tests/test_models_concealer.py +123 -0
- amods-0.1.0/tests/test_models_concealer_registry.py +60 -0
- amods-0.1.0/tests/test_models_denoiser.py +16 -0
- amods-0.1.0/tests/test_models_forecaster.py +20 -0
- amods-0.1.0/tests/test_models_vad.py +74 -0
- amods-0.1.0/tests/test_stream.py +203 -0
amods-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Modan Tailleur
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
amods-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: amods
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Adaptive Masking of Distracting Speech (AMODS)
|
|
5
|
+
Author: Modan Tailleur, Aine Drelingyte, Mathieu Lagrange, Romain Serizel
|
|
6
|
+
Maintainer-email: Modan Tailleur <modan.tailleur@gmail.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 Modan Tailleur
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
30
|
+
Classifier: Programming Language :: Python :: 3
|
|
31
|
+
Classifier: Operating System :: OS Independent
|
|
32
|
+
Classifier: Intended Audience :: Science/Research
|
|
33
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
34
|
+
Classifier: Topic :: Scientific/Engineering
|
|
35
|
+
Requires-Python: >=3.9
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
Requires-Dist: numpy>=1.24
|
|
39
|
+
Requires-Dist: scipy>=1.10
|
|
40
|
+
Requires-Dist: librosa>=0.10
|
|
41
|
+
Requires-Dist: soundfile>=0.12
|
|
42
|
+
Requires-Dist: sounddevice>=0.4
|
|
43
|
+
Requires-Dist: PyYAML>=6.0
|
|
44
|
+
Requires-Dist: torch>=2.0
|
|
45
|
+
Requires-Dist: torchaudio>=2.0
|
|
46
|
+
Requires-Dist: denoiser>=0.1.5
|
|
47
|
+
Requires-Dist: ten_vad>=1.0.6
|
|
48
|
+
Requires-Dist: silero-vad>=5.0
|
|
49
|
+
Requires-Dist: webrtcvad>=2.0
|
|
50
|
+
Provides-Extra: gui
|
|
51
|
+
Provides-Extra: dev
|
|
52
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
53
|
+
Dynamic: license-file
|
|
54
|
+
|
|
55
|
+
# AMODS — Adaptive Masking of Distracting Speech
|
|
56
|
+
|
|
57
|
+
A framework for real-time algorithms that make nearby speech unintelligible to
|
|
58
|
+
listeners.
|
|
59
|
+
|
|
60
|
+
## Background
|
|
61
|
+
|
|
62
|
+
Intelligible background speech is one of the most disruptive sources of noise in shared and open-plan offices. It can increase stress and annoyance, disrupt social interactions, and reduce work performance. Staying focused around distracting conversations is a common concern when working in a shared space.
|
|
63
|
+
|
|
64
|
+
Two approaches are commonly used to address this problem. Sound-masking systems broadcast masking noise throughout the room, but because they do not target periods of speech activity, they need to operate at relatively high levels to be effective. Active noise cancellation (ANC), typically deployed on a per-worker basis through headphones, works well against steady and predictable sounds but is less effective against speech, whose spectral and temporal content varies rapidly.
|
|
65
|
+
|
|
66
|
+
AMODS explores a different approach: adapting the masking to speech activity, both by activating it when speech occurs and by tailoring it to the characteristics of the speech being masked. This approach is complementary to ANC on headphones. We call this general approach *speech concealing*, and the masking sound it produces a *speech concealer*.
|
|
67
|
+
|
|
68
|
+
AMODS is designed to support the development and comparison of different speech-concealing algorithms. See [Using your own concealment algorithm](#using-your-own-concealment-algorithm) to learn how to integrate a new algorithm.
|
|
69
|
+
|
|
70
|
+
The currently included concealment algorithm is **GranSpeechMask** ("granular speech
|
|
71
|
+
masking"). It continuously records short clips of the speaker's recent voice and,
|
|
72
|
+
once a sufficient number have accumulated, replays the clip whose spectral content
|
|
73
|
+
most closely matches the current speech. Using the speaker's own voice as the masking
|
|
74
|
+
signal keeps it spectrally close to the speech being covered, allowing it to operate
|
|
75
|
+
without reaching a distracting level.
|
|
76
|
+
|
|
77
|
+
## Install
|
|
78
|
+
|
|
79
|
+
In a Python 3.9+ environment:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
pip install amods
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This pulls in all runtime dependencies (numpy, scipy, librosa, soundfile, sounddevice,
|
|
86
|
+
PyYAML, torch, torchaudio, denoiser, ten_vad, silero-vad, webrtcvad). If `ten_vad`
|
|
87
|
+
fails to install, you can force reinstall it with:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
pip install -U --force-reinstall -v git+https://github.com/TEN-framework/ten-vad.git
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
To work on amods itself (e.g. to run the test suite), clone the repository and install
|
|
94
|
+
it in editable mode instead:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
git clone https://github.com/modantailleur/amods.git
|
|
98
|
+
cd amods
|
|
99
|
+
pip install -e ".[dev]"
|
|
100
|
+
pytest
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Real-Time Concealing (GUI)
|
|
104
|
+
|
|
105
|
+
The desktop UI provides the primary interface for running the demonstration. It allows
|
|
106
|
+
selection of the microphone, speaker, VAD type and threshold, and denoiser, and displays
|
|
107
|
+
the current memory size:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
amods-gui
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Settings selected in the UI are saved to configuration files
|
|
114
|
+
that are also read by the `amods` CLI (see [Configuration](#configuration)).
|
|
115
|
+
|
|
116
|
+
The GUI needs Tkinter, which ships with Python on most platforms; on some minimal
|
|
117
|
+
Linux installs it needs a separate system package, e.g. `sudo apt install python3-tk`.
|
|
118
|
+
|
|
119
|
+
## Real-Time Concealing (CLI)
|
|
120
|
+
|
|
121
|
+
To conceal speech in real time from the command line, use headphones and run:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
amods
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
By default, the CLI uses the package's bundled default configurations (see
|
|
128
|
+
[Configuration](#configuration)). To use a different configuration, specify it for any
|
|
129
|
+
of the components described in the [Configuration](#configuration) section:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
amods --concealerconfig yourpath/yourconfig.yaml
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The original stream, the concealer output, and the mix between original and concealer
|
|
136
|
+
will be saved to:
|
|
137
|
+
|
|
138
|
+
- `./output/stream_original.wav`
|
|
139
|
+
- `./output/stream_concealer.wav`
|
|
140
|
+
- `./output/stream_mix.wav`
|
|
141
|
+
|
|
142
|
+
## Concealing a Pre-Recorded Audio (Real-Time Emulation)
|
|
143
|
+
|
|
144
|
+
Concealing a pre-recorded audio file provides a way to test the concealer with a
|
|
145
|
+
specified audio input, although this mode has no direct application purpose.
|
|
146
|
+
|
|
147
|
+
To do so, run:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
amods --indir mydir/to/audio/ --infile myaudio.wav
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The original audio, the concealer output, and the mix between original and concealer
|
|
154
|
+
will be saved to:
|
|
155
|
+
|
|
156
|
+
- `./output/myaudio_original.wav`
|
|
157
|
+
- `./output/myaudio_concealer.wav`
|
|
158
|
+
- `./output/myaudio_mix.wav`
|
|
159
|
+
|
|
160
|
+
## Configuration
|
|
161
|
+
|
|
162
|
+
The configuration components are:
|
|
163
|
+
|
|
164
|
+
- `concealer` — the concealment algorithm to run and its parameters (e.g. which
|
|
165
|
+
`concealer_type` is registered under `amods.models.concealer`, how much audio its
|
|
166
|
+
memory holds, and denoising/timing settings). Select its configuration with
|
|
167
|
+
`--concealerconfig`.
|
|
168
|
+
- `stream` — real-time audio I/O settings, including sample rate, channel counts,
|
|
169
|
+
buffer duration, input/output devices, gains, and latency hints. Select its
|
|
170
|
+
configuration with `--streamconfig`.
|
|
171
|
+
- `vad` — voice activity detection settings, including the VAD backend and threshold. Used
|
|
172
|
+
in two places: as the "source" VAD, which determines when speech is present, and
|
|
173
|
+
internally by a concealer algorithm to filter its candidate clips. Select the source
|
|
174
|
+
VAD configuration with `--sourcevadconfig`; an algorithm's internal VAD is specified
|
|
175
|
+
in that algorithm's configuration, for example with `concealer_vad_config` for
|
|
176
|
+
`GranSpeechMask`.
|
|
177
|
+
- `forecaster` — short-term prediction of the next audio frame before it is passed to
|
|
178
|
+
the concealer (currently only `identity`, which passes the frame through unchanged).
|
|
179
|
+
Select its configuration with `--forecasterconfig`.
|
|
180
|
+
|
|
181
|
+
Each configuration option accepts either a configuration name or a path to a YAML
|
|
182
|
+
file. For example, `--concealerconfig default` refers to the `default` concealer
|
|
183
|
+
configuration, while `--concealerconfig path/to/config.yaml` refers to a specific file.
|
|
184
|
+
|
|
185
|
+
When a configuration name is provided, AMODS searches for the corresponding file in
|
|
186
|
+
the following order:
|
|
187
|
+
|
|
188
|
+
1. `./configs/<component>/<name>.yaml`, relative to the current directory. This is
|
|
189
|
+
used when running from a repository checkout or another directory containing a
|
|
190
|
+
`configs/` folder.
|
|
191
|
+
2. `~/.amods/configs/<component>/<name>.yaml`, where settings saved by the GUI are
|
|
192
|
+
stored and can be used regardless of the current directory.
|
|
193
|
+
3. The package's bundled default configuration, which allows `amods` and `amods-gui`
|
|
194
|
+
to run immediately after `pip install` without a local `configs/` directory.
|
|
195
|
+
|
|
196
|
+
If the value already contains a `/`, it is treated as a path and used as provided.
|
|
197
|
+
|
|
198
|
+
## Using amods as a library
|
|
199
|
+
|
|
200
|
+
### Real-time, on your own mic/speaker
|
|
201
|
+
|
|
202
|
+
The following function blocks until Ctrl+C is pressed and then saves the
|
|
203
|
+
three WAV files, equivalent to running `amods` without `--infile`:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from amods.stream import run_stream_mode
|
|
207
|
+
|
|
208
|
+
run_stream_mode(
|
|
209
|
+
outdir="./output",
|
|
210
|
+
outprefix="session1",
|
|
211
|
+
concealerconfig="default",
|
|
212
|
+
streamconfig="default",
|
|
213
|
+
sourcevadconfig="default_source",
|
|
214
|
+
forecasterconfig="default",
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The bundled default stream configuration uses `device_in`/`device_out: null`, which
|
|
219
|
+
selects the system's default microphone and speaker. To target specific devices,
|
|
220
|
+
override the stream configuration with a dictionary (see below) using the indices
|
|
221
|
+
reported by `amods-latency --list-devices`.
|
|
222
|
+
|
|
223
|
+
For finer control, such as specifying a stop condition or processing audio as it arrives,
|
|
224
|
+
drive `Stream` with `sounddevice` directly. This is the approach used internally by
|
|
225
|
+
`run_stream_mode`:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
import sounddevice as sd
|
|
229
|
+
from amods import Stream
|
|
230
|
+
|
|
231
|
+
stream = Stream("default_source", "default", "default", "default", is_stream=True)
|
|
232
|
+
stream.reset_state()
|
|
233
|
+
sc = stream.stream_config
|
|
234
|
+
|
|
235
|
+
with sd.InputStream(samplerate=sc["sr"], blocksize=stream.buffer_size, dtype=sc["dtype"],
|
|
236
|
+
channels=sc["channels_in"], device=sc["device_in"],
|
|
237
|
+
callback=stream.input_callback, latency=sc.get("input_latency", "low")), \
|
|
238
|
+
sd.OutputStream(samplerate=sc["sr"], dtype=sc["dtype"], channels=sc["channels_out"],
|
|
239
|
+
device=sc["device_out"], callback=stream.output_callback,
|
|
240
|
+
latency=sc.get("output_latency", "high")):
|
|
241
|
+
sd.sleep(10_000) # run for 10s; swap in your own stop condition
|
|
242
|
+
|
|
243
|
+
# stream.rec_original / rec_concealer / rec_mix now hold the recorded chunks,
|
|
244
|
+
# as they do after run_stream_mode; concatenate and write with soundfile as needed.
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### A pre-recorded audio file
|
|
248
|
+
|
|
249
|
+
The following function reads a WAV file, processes it through the same
|
|
250
|
+
per-chunk pipeline as real-time mode, and writes the three output WAVs, equivalent to
|
|
251
|
+
`amods --infile ...`:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from amods.stream import run_file_mode
|
|
255
|
+
|
|
256
|
+
run_file_mode(
|
|
257
|
+
indir="./audios/",
|
|
258
|
+
infile="voice_sample.wav",
|
|
259
|
+
outdir="./output",
|
|
260
|
+
outprefix="", # empty -> uses the input filename as the prefix
|
|
261
|
+
concealerconfig="default",
|
|
262
|
+
streamconfig="default",
|
|
263
|
+
sourcevadconfig="default_source",
|
|
264
|
+
forecasterconfig="default",
|
|
265
|
+
)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
If the audio is already available as a NumPy array in memory (mono `float32`, at the
|
|
269
|
+
stream configuration's sample rate), rather than as a file on disk, pass it to
|
|
270
|
+
`Stream.callback` in fixed-size blocks. This is the approach used internally by
|
|
271
|
+
`run_file_mode`:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
import numpy as np
|
|
275
|
+
from amods import Stream
|
|
276
|
+
|
|
277
|
+
stream = Stream("default_source", "default", "default", "default", is_stream=False)
|
|
278
|
+
stream.reset_state()
|
|
279
|
+
|
|
280
|
+
y = my_audio_array.astype(np.float32)
|
|
281
|
+
n = len(y)
|
|
282
|
+
n_blocks = int(np.ceil(n / stream.buffer_size))
|
|
283
|
+
y = np.pad(y, (0, n_blocks * stream.buffer_size - n))
|
|
284
|
+
|
|
285
|
+
for b in range(n_blocks):
|
|
286
|
+
block = y[b * stream.buffer_size : (b + 1) * stream.buffer_size]
|
|
287
|
+
indata = block.reshape(-1, 1)
|
|
288
|
+
outdata = np.zeros((stream.buffer_size, 2), dtype=np.float32)
|
|
289
|
+
stream.callback(indata, outdata, stream.buffer_size, None, None)
|
|
290
|
+
|
|
291
|
+
concealed = np.concatenate(stream.rec_concealer, axis=0)[:n] # concealer output only
|
|
292
|
+
mix = np.concatenate(stream.rec_mix, axis=0)[:n] # original + concealer, mixed
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Customizing configs from code
|
|
296
|
+
|
|
297
|
+
Each `*_config_name` argument accepts either a name/path (resolved as described above)
|
|
298
|
+
**or an already-built dictionary**, which is used directly. This permits YAML to be
|
|
299
|
+
omitted entirely, or allows selected fields to be overridden in a named configuration:
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from amods.config import load_config
|
|
303
|
+
|
|
304
|
+
config = load_config("concealer", "default")
|
|
305
|
+
config["denoise"] = False # tweak just this one field
|
|
306
|
+
stream = Stream(source_vad_config_name="default_source",
|
|
307
|
+
concealer_config_name=config,
|
|
308
|
+
stream_config_name="default",
|
|
309
|
+
forecaster_config_name="default",
|
|
310
|
+
is_stream=False)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
The same applies one level down: a concealer configuration's `concealer_vad_config`
|
|
314
|
+
field can also be a name/path or an inline dictionary. If only one component, such as a
|
|
315
|
+
VAD, is required rather than the complete `Stream`, the lower-level functions
|
|
316
|
+
(`amods.models.vad.select_vad_model`, `amods.models.concealer.select_concealer_model`,
|
|
317
|
+
etc.) already take plain config dicts directly.
|
|
318
|
+
|
|
319
|
+
### Using your own concealment algorithm
|
|
320
|
+
|
|
321
|
+
`concealer_type` in a concealer config selects which registered algorithm runs (e.g.
|
|
322
|
+
`granspeechmask`). To add a custom algorithm, implement `amods.models.concealer.ConcealerModel`
|
|
323
|
+
and decorate it with `@register_concealer("your_type_name")` — see the module docstring
|
|
324
|
+
in `amods/models/concealer/base.py` for the exact contract. No changes to `Stream`, the
|
|
325
|
+
CLI, or the examples above are required; reference the `type_name` in a concealer
|
|
326
|
+
configuration.
|
|
327
|
+
|
|
328
|
+
## Testing mic/speaker devices
|
|
329
|
+
|
|
330
|
+
The `amods-latency` tool can be used to identify available audio devices and verify a
|
|
331
|
+
microphone/speaker pair before starting a real-time stream. First, list the available
|
|
332
|
+
devices and their indices:
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
amods-latency --list-devices
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Then provide the input and output device indices to test a specific pair. The tool
|
|
339
|
+
verifies that the pair is functioning and reports the PortAudio-reported input/output
|
|
340
|
+
latency:
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
amods-latency --device-in 15 --device-out 11
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Authors
|
|
347
|
+
|
|
348
|
+
- Modan Tailleur
|
|
349
|
+
- Aine Drelingyte
|
|
350
|
+
- Mathieu Lagrange
|
|
351
|
+
- Romain Serizel
|
amods-0.1.0/README.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# AMODS — Adaptive Masking of Distracting Speech
|
|
2
|
+
|
|
3
|
+
A framework for real-time algorithms that make nearby speech unintelligible to
|
|
4
|
+
listeners.
|
|
5
|
+
|
|
6
|
+
## Background
|
|
7
|
+
|
|
8
|
+
Intelligible background speech is one of the most disruptive sources of noise in shared and open-plan offices. It can increase stress and annoyance, disrupt social interactions, and reduce work performance. Staying focused around distracting conversations is a common concern when working in a shared space.
|
|
9
|
+
|
|
10
|
+
Two approaches are commonly used to address this problem. Sound-masking systems broadcast masking noise throughout the room, but because they do not target periods of speech activity, they need to operate at relatively high levels to be effective. Active noise cancellation (ANC), typically deployed on a per-worker basis through headphones, works well against steady and predictable sounds but is less effective against speech, whose spectral and temporal content varies rapidly.
|
|
11
|
+
|
|
12
|
+
AMODS explores a different approach: adapting the masking to speech activity, both by activating it when speech occurs and by tailoring it to the characteristics of the speech being masked. This approach is complementary to ANC on headphones. We call this general approach *speech concealing*, and the masking sound it produces a *speech concealer*.
|
|
13
|
+
|
|
14
|
+
AMODS is designed to support the development and comparison of different speech-concealing algorithms. See [Using your own concealment algorithm](#using-your-own-concealment-algorithm) to learn how to integrate a new algorithm.
|
|
15
|
+
|
|
16
|
+
The currently included concealment algorithm is **GranSpeechMask** ("granular speech
|
|
17
|
+
masking"). It continuously records short clips of the speaker's recent voice and,
|
|
18
|
+
once a sufficient number have accumulated, replays the clip whose spectral content
|
|
19
|
+
most closely matches the current speech. Using the speaker's own voice as the masking
|
|
20
|
+
signal keeps it spectrally close to the speech being covered, allowing it to operate
|
|
21
|
+
without reaching a distracting level.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
In a Python 3.9+ environment:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
pip install amods
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This pulls in all runtime dependencies (numpy, scipy, librosa, soundfile, sounddevice,
|
|
32
|
+
PyYAML, torch, torchaudio, denoiser, ten_vad, silero-vad, webrtcvad). If `ten_vad`
|
|
33
|
+
fails to install, you can force reinstall it with:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
pip install -U --force-reinstall -v git+https://github.com/TEN-framework/ten-vad.git
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
To work on amods itself (e.g. to run the test suite), clone the repository and install
|
|
40
|
+
it in editable mode instead:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
git clone https://github.com/modantailleur/amods.git
|
|
44
|
+
cd amods
|
|
45
|
+
pip install -e ".[dev]"
|
|
46
|
+
pytest
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Real-Time Concealing (GUI)
|
|
50
|
+
|
|
51
|
+
The desktop UI provides the primary interface for running the demonstration. It allows
|
|
52
|
+
selection of the microphone, speaker, VAD type and threshold, and denoiser, and displays
|
|
53
|
+
the current memory size:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
amods-gui
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Settings selected in the UI are saved to configuration files
|
|
60
|
+
that are also read by the `amods` CLI (see [Configuration](#configuration)).
|
|
61
|
+
|
|
62
|
+
The GUI needs Tkinter, which ships with Python on most platforms; on some minimal
|
|
63
|
+
Linux installs it needs a separate system package, e.g. `sudo apt install python3-tk`.
|
|
64
|
+
|
|
65
|
+
## Real-Time Concealing (CLI)
|
|
66
|
+
|
|
67
|
+
To conceal speech in real time from the command line, use headphones and run:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
amods
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
By default, the CLI uses the package's bundled default configurations (see
|
|
74
|
+
[Configuration](#configuration)). To use a different configuration, specify it for any
|
|
75
|
+
of the components described in the [Configuration](#configuration) section:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
amods --concealerconfig yourpath/yourconfig.yaml
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The original stream, the concealer output, and the mix between original and concealer
|
|
82
|
+
will be saved to:
|
|
83
|
+
|
|
84
|
+
- `./output/stream_original.wav`
|
|
85
|
+
- `./output/stream_concealer.wav`
|
|
86
|
+
- `./output/stream_mix.wav`
|
|
87
|
+
|
|
88
|
+
## Concealing a Pre-Recorded Audio (Real-Time Emulation)
|
|
89
|
+
|
|
90
|
+
Concealing a pre-recorded audio file provides a way to test the concealer with a
|
|
91
|
+
specified audio input, although this mode has no direct application purpose.
|
|
92
|
+
|
|
93
|
+
To do so, run:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
amods --indir mydir/to/audio/ --infile myaudio.wav
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The original audio, the concealer output, and the mix between original and concealer
|
|
100
|
+
will be saved to:
|
|
101
|
+
|
|
102
|
+
- `./output/myaudio_original.wav`
|
|
103
|
+
- `./output/myaudio_concealer.wav`
|
|
104
|
+
- `./output/myaudio_mix.wav`
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
The configuration components are:
|
|
109
|
+
|
|
110
|
+
- `concealer` — the concealment algorithm to run and its parameters (e.g. which
|
|
111
|
+
`concealer_type` is registered under `amods.models.concealer`, how much audio its
|
|
112
|
+
memory holds, and denoising/timing settings). Select its configuration with
|
|
113
|
+
`--concealerconfig`.
|
|
114
|
+
- `stream` — real-time audio I/O settings, including sample rate, channel counts,
|
|
115
|
+
buffer duration, input/output devices, gains, and latency hints. Select its
|
|
116
|
+
configuration with `--streamconfig`.
|
|
117
|
+
- `vad` — voice activity detection settings, including the VAD backend and threshold. Used
|
|
118
|
+
in two places: as the "source" VAD, which determines when speech is present, and
|
|
119
|
+
internally by a concealer algorithm to filter its candidate clips. Select the source
|
|
120
|
+
VAD configuration with `--sourcevadconfig`; an algorithm's internal VAD is specified
|
|
121
|
+
in that algorithm's configuration, for example with `concealer_vad_config` for
|
|
122
|
+
`GranSpeechMask`.
|
|
123
|
+
- `forecaster` — short-term prediction of the next audio frame before it is passed to
|
|
124
|
+
the concealer (currently only `identity`, which passes the frame through unchanged).
|
|
125
|
+
Select its configuration with `--forecasterconfig`.
|
|
126
|
+
|
|
127
|
+
Each configuration option accepts either a configuration name or a path to a YAML
|
|
128
|
+
file. For example, `--concealerconfig default` refers to the `default` concealer
|
|
129
|
+
configuration, while `--concealerconfig path/to/config.yaml` refers to a specific file.
|
|
130
|
+
|
|
131
|
+
When a configuration name is provided, AMODS searches for the corresponding file in
|
|
132
|
+
the following order:
|
|
133
|
+
|
|
134
|
+
1. `./configs/<component>/<name>.yaml`, relative to the current directory. This is
|
|
135
|
+
used when running from a repository checkout or another directory containing a
|
|
136
|
+
`configs/` folder.
|
|
137
|
+
2. `~/.amods/configs/<component>/<name>.yaml`, where settings saved by the GUI are
|
|
138
|
+
stored and can be used regardless of the current directory.
|
|
139
|
+
3. The package's bundled default configuration, which allows `amods` and `amods-gui`
|
|
140
|
+
to run immediately after `pip install` without a local `configs/` directory.
|
|
141
|
+
|
|
142
|
+
If the value already contains a `/`, it is treated as a path and used as provided.
|
|
143
|
+
|
|
144
|
+
## Using amods as a library
|
|
145
|
+
|
|
146
|
+
### Real-time, on your own mic/speaker
|
|
147
|
+
|
|
148
|
+
The following function blocks until Ctrl+C is pressed and then saves the
|
|
149
|
+
three WAV files, equivalent to running `amods` without `--infile`:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from amods.stream import run_stream_mode
|
|
153
|
+
|
|
154
|
+
run_stream_mode(
|
|
155
|
+
outdir="./output",
|
|
156
|
+
outprefix="session1",
|
|
157
|
+
concealerconfig="default",
|
|
158
|
+
streamconfig="default",
|
|
159
|
+
sourcevadconfig="default_source",
|
|
160
|
+
forecasterconfig="default",
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The bundled default stream configuration uses `device_in`/`device_out: null`, which
|
|
165
|
+
selects the system's default microphone and speaker. To target specific devices,
|
|
166
|
+
override the stream configuration with a dictionary (see below) using the indices
|
|
167
|
+
reported by `amods-latency --list-devices`.
|
|
168
|
+
|
|
169
|
+
For finer control, such as specifying a stop condition or processing audio as it arrives,
|
|
170
|
+
drive `Stream` with `sounddevice` directly. This is the approach used internally by
|
|
171
|
+
`run_stream_mode`:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
import sounddevice as sd
|
|
175
|
+
from amods import Stream
|
|
176
|
+
|
|
177
|
+
stream = Stream("default_source", "default", "default", "default", is_stream=True)
|
|
178
|
+
stream.reset_state()
|
|
179
|
+
sc = stream.stream_config
|
|
180
|
+
|
|
181
|
+
with sd.InputStream(samplerate=sc["sr"], blocksize=stream.buffer_size, dtype=sc["dtype"],
|
|
182
|
+
channels=sc["channels_in"], device=sc["device_in"],
|
|
183
|
+
callback=stream.input_callback, latency=sc.get("input_latency", "low")), \
|
|
184
|
+
sd.OutputStream(samplerate=sc["sr"], dtype=sc["dtype"], channels=sc["channels_out"],
|
|
185
|
+
device=sc["device_out"], callback=stream.output_callback,
|
|
186
|
+
latency=sc.get("output_latency", "high")):
|
|
187
|
+
sd.sleep(10_000) # run for 10s; swap in your own stop condition
|
|
188
|
+
|
|
189
|
+
# stream.rec_original / rec_concealer / rec_mix now hold the recorded chunks,
|
|
190
|
+
# as they do after run_stream_mode; concatenate and write with soundfile as needed.
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### A pre-recorded audio file
|
|
194
|
+
|
|
195
|
+
The following function reads a WAV file, processes it through the same
|
|
196
|
+
per-chunk pipeline as real-time mode, and writes the three output WAVs, equivalent to
|
|
197
|
+
`amods --infile ...`:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from amods.stream import run_file_mode
|
|
201
|
+
|
|
202
|
+
run_file_mode(
|
|
203
|
+
indir="./audios/",
|
|
204
|
+
infile="voice_sample.wav",
|
|
205
|
+
outdir="./output",
|
|
206
|
+
outprefix="", # empty -> uses the input filename as the prefix
|
|
207
|
+
concealerconfig="default",
|
|
208
|
+
streamconfig="default",
|
|
209
|
+
sourcevadconfig="default_source",
|
|
210
|
+
forecasterconfig="default",
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
If the audio is already available as a NumPy array in memory (mono `float32`, at the
|
|
215
|
+
stream configuration's sample rate), rather than as a file on disk, pass it to
|
|
216
|
+
`Stream.callback` in fixed-size blocks. This is the approach used internally by
|
|
217
|
+
`run_file_mode`:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
import numpy as np
|
|
221
|
+
from amods import Stream
|
|
222
|
+
|
|
223
|
+
stream = Stream("default_source", "default", "default", "default", is_stream=False)
|
|
224
|
+
stream.reset_state()
|
|
225
|
+
|
|
226
|
+
y = my_audio_array.astype(np.float32)
|
|
227
|
+
n = len(y)
|
|
228
|
+
n_blocks = int(np.ceil(n / stream.buffer_size))
|
|
229
|
+
y = np.pad(y, (0, n_blocks * stream.buffer_size - n))
|
|
230
|
+
|
|
231
|
+
for b in range(n_blocks):
|
|
232
|
+
block = y[b * stream.buffer_size : (b + 1) * stream.buffer_size]
|
|
233
|
+
indata = block.reshape(-1, 1)
|
|
234
|
+
outdata = np.zeros((stream.buffer_size, 2), dtype=np.float32)
|
|
235
|
+
stream.callback(indata, outdata, stream.buffer_size, None, None)
|
|
236
|
+
|
|
237
|
+
concealed = np.concatenate(stream.rec_concealer, axis=0)[:n] # concealer output only
|
|
238
|
+
mix = np.concatenate(stream.rec_mix, axis=0)[:n] # original + concealer, mixed
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Customizing configs from code
|
|
242
|
+
|
|
243
|
+
Each `*_config_name` argument accepts either a name/path (resolved as described above)
|
|
244
|
+
**or an already-built dictionary**, which is used directly. This permits YAML to be
|
|
245
|
+
omitted entirely, or allows selected fields to be overridden in a named configuration:
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from amods.config import load_config
|
|
249
|
+
|
|
250
|
+
config = load_config("concealer", "default")
|
|
251
|
+
config["denoise"] = False # tweak just this one field
|
|
252
|
+
stream = Stream(source_vad_config_name="default_source",
|
|
253
|
+
concealer_config_name=config,
|
|
254
|
+
stream_config_name="default",
|
|
255
|
+
forecaster_config_name="default",
|
|
256
|
+
is_stream=False)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The same applies one level down: a concealer configuration's `concealer_vad_config`
|
|
260
|
+
field can also be a name/path or an inline dictionary. If only one component, such as a
|
|
261
|
+
VAD, is required rather than the complete `Stream`, the lower-level functions
|
|
262
|
+
(`amods.models.vad.select_vad_model`, `amods.models.concealer.select_concealer_model`,
|
|
263
|
+
etc.) already take plain config dicts directly.
|
|
264
|
+
|
|
265
|
+
### Using your own concealment algorithm
|
|
266
|
+
|
|
267
|
+
`concealer_type` in a concealer config selects which registered algorithm runs (e.g.
|
|
268
|
+
`granspeechmask`). To add a custom algorithm, implement `amods.models.concealer.ConcealerModel`
|
|
269
|
+
and decorate it with `@register_concealer("your_type_name")` — see the module docstring
|
|
270
|
+
in `amods/models/concealer/base.py` for the exact contract. No changes to `Stream`, the
|
|
271
|
+
CLI, or the examples above are required; reference the `type_name` in a concealer
|
|
272
|
+
configuration.
|
|
273
|
+
|
|
274
|
+
## Testing mic/speaker devices
|
|
275
|
+
|
|
276
|
+
The `amods-latency` tool can be used to identify available audio devices and verify a
|
|
277
|
+
microphone/speaker pair before starting a real-time stream. First, list the available
|
|
278
|
+
devices and their indices:
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
amods-latency --list-devices
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Then provide the input and output device indices to test a specific pair. The tool
|
|
285
|
+
verifies that the pair is functioning and reports the PortAudio-reported input/output
|
|
286
|
+
latency:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
amods-latency --device-in 15 --device-out 11
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Authors
|
|
293
|
+
|
|
294
|
+
- Modan Tailleur
|
|
295
|
+
- Aine Drelingyte
|
|
296
|
+
- Mathieu Lagrange
|
|
297
|
+
- Romain Serizel
|