├── Figure_1.png
├── requirements.txt
├── generate_example.py
├── LICENSE
├── README.md
└── sc_wind_noise_generator.py
/Figure_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/audiolabs/SC-Wind-Noise-Generator/HEAD/Figure_1.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | matplotlib==3.6.2
2 | numpy==1.23.5
3 | scipy==1.11.1
4 | sounddevice==0.4.6
5 | soundfile==0.12.1
6 | spectrum==0.8.1
7 |
--------------------------------------------------------------------------------
/generate_example.py:
--------------------------------------------------------------------------------
1 | """Example to generate a wind noise signal."""
2 |
3 | import numpy as np
4 | from sc_wind_noise_generator import WindNoiseGenerator as wng
5 |
6 | # Parameters
7 | DURATION = 10 # Duration in seconds
8 | FS = 48000 # Sample frequency in Hz
9 | GUSTINESS = 10 # Number of speed points. One yields constant wind. High values yield gusty wind (more than 10 can sound unnatural).
10 | WIND_PROFILE_OUTDOOR = np.array([3.45, 6.74, 5.65, 6.34, 4.00,
11 | 5.88, 3.26, 3.19, 4.78, 4.16,
12 | 4.67, 4.69, 4.61, 6.53, 6.05]) # Wind speed data example
13 | SEED = 1 # Seed for random sequence regeneration
14 |
15 | # Generate wind noise
16 | wn = wng(fs=FS, duration=DURATION, generate=True,
17 | wind_profile=WIND_PROFILE_OUTDOOR,
18 | gustiness=GUSTINESS,
19 | short_term_var=True, start_seed=SEED)
20 | wn_signal, wind_profile = wn.generate_wind_noise()
21 |
22 | # Save signal in .wav file
23 | wn.save_signal(wn_signal, filename='wind_noise.wav', num_ch=1, fs=16000)
24 |
25 | # Plot signal
26 | wn.plot_signals(wn_signal, wind_profile)
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Friedrich-Alexander-Universität Erlangen-Nürnberg, Germany
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SC-Wind-Noise-Generator
2 |
3 | ## Introduction
4 | The SC-Wind-Noise-Generator is a Python-based framework designed to generate synthetic wind noise based on a wind speed profile. It provides a simple and flexible way to simulate wind noise for various applications, such as audio signal processing, noise reduction, audio production, video game development, and virtual reality experiences. The SC-Wind-Noise-Generator ensures that each generated sample is statistically independent from the others, allowing for realistic and varied wind noise simulations. This is particularly suitable for training deep learning-based solutions (e.g., wind noise reduction).
5 |
6 | Note: By wind noise, we refer to the wind turbulent pressure fluctuations that generate low-frequency rumbling sound in microphone signals, and not particularly to the aeroacoustic sound generated by wind (e.g., whistling in pipes or electric cables, rustling leaves, etc.).
7 |
8 | For sound designers and VR/game developers, we recommend setting the attribute `short_term_var=False` to simulate a more "pleasant" wind noise for your application. More details can be found in the Customization section.
9 |
10 | This repository contains the accompanying code of the following paper:
11 | _______________________________________________________________________
12 | **Simulating wind noise with airflow speed-dependent characteristics**
13 | *D. Mirabilii1, A. Lodermeyer1, F. Czwielong2, S. Becker2 and E. A. P. Habets1*
14 | *1International Audio Laboratories Erlangen*
15 | *2Aerodynamics and Acoustics, Institute of Fluid Mechanics, FAU Erlangen-Nürnberg*
16 | in Proc. Intl. Workshop Acoust. Signal Enhancement (IWAENC), Bamberg, 2022
17 | ________________________________________________________________________
18 |
19 | For a detailed description of the fundamental methods used in the framework and for audio examples, please refer to the [webpage](https://www.audiolabs-erlangen.de/resources/2022-IWAENC-SWN).
20 |
21 | This README file provides an overview of the framework, including its installation instructions, basic usage, and customization options.
22 |
23 |
24 | ## Installation
25 | To use the SC-Wind-Noise-Generator, follow these steps:
26 |
27 | 1. Make sure you have Python 3.6 or later installed on your system.
28 | 2. Clone the repository from GitHub:
29 |
30 | ```bash
31 | git clone https://github.com/audiolabs/SC-Wind-Noise-Generator.git
32 | ```
33 |
34 | 3. Change into the project directory:
35 |
36 | ```bash
37 | cd SC-Wind-Noise-Generator
38 | ```
39 |
40 | 4. Install the required dependencies using pip:
41 |
42 | ```bash
43 | pip install -r requirements.txt
44 | ```
45 |
46 | 5. Once the dependencies are installed, you can use the framework.
47 |
48 | ## Usage
49 | To generate synthetic wind noise, follow these steps:
50 |
51 | 1. Import the `WindNoiseGenerator` class from the framework:
52 |
53 | ```python
54 | from sc_wind_noise_generator import WindNoiseGenerator
55 | ```
56 |
57 | 2. Create an instance of the `WindNoiseGenerator` class using the default settings:
58 |
59 | ```python
60 | generator = WindNoiseGenerator()
61 | ```
62 |
63 | 3. Or set the parameters (ex. sampling frequency and duration) for which you want to generate wind noise:
64 |
65 | ```python
66 | generator = WindNoiseGenerator(fs=48000, duration=5, generate=True, wind_profile=None, gustiness=3, start_seed=None)
67 | ```
68 | (see Customization for further details)
69 | 4. Generate the wind noise waveform and the wind speed profile (if not user-defined):
70 |
71 | ```python
72 | waveform, wind_profile = generator.generate_wind_noise()
73 | ```
74 |
75 | 5. You can now use the generated `waveform` for further processing or save it to a file:
76 | ```python
77 | generator.save_signal(waveform)
78 | ```
79 |
80 | 6. You can also plot the waveform and the wind profile:
81 |
82 | ```python
83 | generator.plot_signals(waveform, wind_profile)
84 | ```
85 |
86 |
87 |
88 |
89 | The repo contains a basic example `generate_example.py` that generates wind noise with a specific wind speed profile, saves to a .wav file, and plot the result.
90 |
91 | ## Customization
92 | The `WindNoiseGenerator` class provides various customization options to tailor the wind noise generation to your specific needs. Change the attribute's value according to the following description.
93 |
94 | - Attributes:
95 | - `fs` sampling frequency in Hz. The framework works at 48 kHz, but if a different sampling frequency is selected, the final waveform is automatically resampled.
96 | - `duration` duration in seconds of the generated wind noise sample.
97 | - `generate` boolean for automatically generating the wind speed profile (if True, the user-defined wind speed profile attribute is ignored).
98 | - `wind_profile` user-defined numpy array that contains wind speed values over time (to use it, `generate=False`).
99 | - `gustiness` parameter to change how often the wind speed changes over time if `generate=True`. For example, `gustiness=1` yields constant wind speed, and `gustiness=10` yields highly-variable wind speed over the duration of the sample. Ignored if `generate=False`.
100 | - `short_term_var` set to True if you want to simulate distortions created by turbulent pressure fluctuations (suggested for, e.g., wind noise reduction purposes), or False to generate a smoother sound (suggested for, e.g., sound designers, and VR/game developers).
101 | - `start_seed` set to None to generate different and independent realizations of wind noise, even when the same attributes (e.g., the wind speed profile) are selected. Otherwise, choose an integer number to generate reproducible wind noise samples.
102 |
103 | Here are some examples of available methods:
104 |
105 | - Play generated wind noise signal:
106 | - Use the `play_signal` method to play the generated wind noise signal.
107 |
108 |
109 | - Save generated wind noise signal:
110 | - Use the `save_signal` method to save the generated wind noise signal with a specific name and location.
111 | - Parameters: `filename` path and file name, `print_log` print message of successful save, `num_ch` 1 for mono and 2 for "stereo" (copy of the sample on both left and right channels), `fs` sampling frequency
112 | - Example:
113 | ```python
114 | generator.save_signal(waveform, filename='wind_noise.wav', num_ch=1, fs=16000)
115 | ```
116 |
117 | - Plot generated wind noise signal:
118 | - Use the `plot_signal` method to plot the generated wind noise signal waveform, spectrogram and the associated wind speed profile.
119 | - Example:
120 | ```python
121 | generator.plot_signals(waveform, wind_profile)
122 | ```
123 | The basic functionality of the framework is shown in `generate_example.py`, which generates wind noise with a specific wind speed profile, saves it to a .wav file, and plot the result. Please refer to the documentation or source code for more details on customization options and available methods.
124 |
125 | ## Contributing
126 | If you encounter any issues, have suggestions for improvements, or would like to add new features, please submit an issue on GitHub or contact the maintainers.
127 |
128 | ## License
129 | The SC-Wind-Noise-Generator is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information.
130 |
131 | ## Contact
132 | If you have any questions, suggestions, or feedback, please contact the project maintainers:
133 |
134 | - Daniele Mirabilii [danielemirabilii@gmail.com](danielemirabilii@gmail.com)
135 | - Emanuël Habets [emanuel.habets@audiolabs-erlangen.de](emanuel.habets@audiolabs-erlangen.de)
--------------------------------------------------------------------------------
/sc_wind_noise_generator.py:
--------------------------------------------------------------------------------
1 | """
2 | Single-Channel Wind Noise Generator
3 |
4 | Authors : Daniele Mirabilii and Emanuël Habets
5 |
6 | Reference : D. Mirabilii, A. Lodermeyer, F. Czwielong, S. Becker and E.A P. Habets,
7 | Simulating wind noise with airflow speed-dependent characteristics,
8 | Proc. of International Workshop on Acoustic Signal Enhancement (IWAENC), 2022.
9 |
10 | Copyright (C) 2023 Friedrich-Alexander-Universität Erlangen-Nürnberg, Germany
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining
13 | a copy of this software and associated documentation files (the
14 | "Software"), to deal in the Software without restriction, including
15 | without limitation the rights to use, copy, modify, merge, publish,
16 | distribute, sublicense, and/or sell copies of the Software, and to
17 | permit persons to whom the Software is furnished to do so, subject to
18 | the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be
21 | included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 | """
31 |
32 | import time
33 | import numpy as np
34 | import scipy as sp
35 | from scipy import signal
36 | import matplotlib.pyplot as plt
37 | import spectrum
38 | import soundfile as sf
39 | import sounddevice as sd
40 |
41 | class WindNoiseGenerator:
42 | """Wind Noise Generator Class"""
43 |
44 | def __init__(self, fs=48000, duration=5, generate=True, wind_profile=None, gustiness=3, short_term_var=True, start_seed=None):
45 | """Initizalize object"""
46 |
47 | self.fs = fs
48 | self.duration = duration
49 | self.samples = fs * duration
50 | self.generate = generate
51 | self.gustiness = gustiness
52 | self.wind_profile = wind_profile
53 | self.short_term_var = short_term_var
54 | if start_seed is not None:
55 | np.random.seed(start_seed)
56 |
57 | def generate_wind_noise(self):
58 | """Generate single-channel wind noise by filtering excitation signal"""
59 |
60 | if self.generate:
61 | wind_profile = self._generate_wind_speed_profile()
62 | else:
63 | wind_profile = self._import_wind_speed_profile()
64 |
65 | exc = self.generate_excitation_signal(wind_profile)
66 | exc_filtered = self._filter(exc, wind_profile, 2048)
67 | exc_filtered = 0.95*exc_filtered / \
68 | max(np.abs(exc_filtered))
69 |
70 | return exc_filtered, wind_profile
71 |
72 | def generate_excitation_signal(self, wind_profile):
73 | """Generate excitation signal"""
74 |
75 | window_size = 128
76 | hops = window_size // 2 # overlap
77 | hann_window = np.hanning(window_size) # hanning window
78 |
79 | wgn = np.concatenate(
80 | (np.zeros(window_size), np.random.randn(self.samples), np.zeros(window_size)))
81 | wgn_length = len(wgn)
82 |
83 | lt_var = self._generate_long_term_variance(wind_profile)
84 | lt_var = np.concatenate((np.zeros(window_size), lt_var, np.zeros(window_size)))
85 |
86 | st_var = self._generate_short_term_variance_garch(wind_profile)
87 | cond_var = np.abs(st_var)
88 |
89 | num_windows = (wgn_length - window_size) // hops + 1
90 | exc = np.zeros(wgn_length)
91 |
92 | for time_frame in range(num_windows-1):
93 | start_idx = time_frame * hops
94 | end_idx = start_idx + window_size
95 | idx = np.arange(start_idx, end_idx)
96 |
97 | gain_ltst = lt_var[idx]
98 | if self.short_term_var:
99 | gain_ltst *= np.sqrt(cond_var[time_frame])
100 | noise_seg_ltst = gain_ltst * wgn[idx] * hann_window
101 | exc[idx] += noise_seg_ltst
102 |
103 | exc = exc[window_size:-window_size]
104 |
105 | return exc
106 |
107 | def _generate_short_term_variance_garch(self, wind_profile):
108 | """Generate short-term variance of GARCH process"""
109 |
110 | window_size = 128
111 | hops = window_size // 2 # overlap
112 |
113 | profile = np.concatenate(
114 | (2 * np.ones(window_size), wind_profile, 2 * np.ones(window_size)))
115 | profile_length = len(profile)
116 |
117 | num_windows = (profile_length - window_size) // hops + 1
118 | st_var = np.zeros(num_windows)
119 | cond_var = np.zeros(num_windows)
120 |
121 | for time_frame in range(num_windows):
122 | start_idx = time_frame * hops
123 | end_idx = start_idx + window_size
124 | idx = np.arange(start_idx, end_idx)
125 |
126 | speed = np.clip(np.mean(profile[idx]), 2, 18)
127 | alpha, beta, omega = self._speed2par(speed)
128 |
129 | if alpha + beta > 1:
130 | beta = 0
131 |
132 | cond_var[time_frame] = omega + alpha * \
133 | st_var[time_frame-1]**2 + beta*(cond_var[time_frame-1])
134 | st_var[time_frame] = np.sqrt(np.abs(cond_var[time_frame])) * \
135 | np.random.randn()
136 |
137 | return st_var/max(np.abs(st_var))
138 |
139 | def _generate_long_term_variance(self, wind_profile):
140 | """Generate long-term variance"""
141 |
142 | # Regression parameter noise variance/wind speed
143 | regression_coeff = np.array([8.00071114414022, -220.332082908370])
144 |
145 | # Long-term noise variance based on wind speed profile in dB scale
146 | variance_profile_db = np.polyval(regression_coeff, wind_profile)
147 |
148 | # Long-term noise variance in linear scale
149 | variance_profile = 10 ** (variance_profile_db / 10)
150 | var_lt = np.sqrt(np.abs(variance_profile)) # long-term gain
151 |
152 | return var_lt
153 |
154 | def _generate_wind_speed_profile(self, b_par=2, a_par=2):
155 | """Generate the wind speed profile by sampling a Weibull distribution"""
156 |
157 | speed_points = int(
158 | self.gustiness) # gustiness, 1 = constant speed, 10 = highly-variable speed
159 |
160 | # Sample from the Weibull distribution (change b and a for different distributions)
161 | wind_speed_profile_lt = b_par * np.random.weibull(a_par, speed_points)
162 |
163 | # Interpolate speed values as required audio samples
164 | wind_speed_profile = sp.signal.resample(
165 | wind_speed_profile_lt, self.samples)
166 |
167 | # Additive speed fluctuations
168 | fluctuations = 10 * np.random.randn(self.samples)
169 |
170 | # Smoothing of the fluctuations
171 | hann_window = np.hanning(self.fs * 100e-3)
172 | hann_window /= sum(hann_window) # hanning window for the smoothing
173 | fluctuations = sp.signal.lfilter(hann_window, 1, fluctuations)
174 |
175 | # Add the fluctuations to the generated wind speed profile
176 | wind_speed_profile += fluctuations
177 |
178 | return wind_speed_profile
179 |
180 | def _import_wind_speed_profile(self):
181 | """Read the wind speed profile from input"""
182 |
183 | wind_speed_profile_lt = self.wind_profile # load speed values
184 |
185 | # Interpolate speed values as required audio samples
186 | wind_speed_profile = sp.signal.resample(
187 | wind_speed_profile_lt, self.samples)
188 | fluctuations = 10 * np.random.randn(self.samples) # additive speed fluctuations
189 |
190 | # Smoothing of the fluctuations
191 | hann_window = np.hanning(self.fs * 100e-3)
192 | hann_window /= sum(hann_window) # hanning window for the smoothing
193 | fluctuations = sp.signal.lfilter(hann_window, 1, fluctuations)
194 |
195 | # Add the fluctuations to the generated wind speed profile
196 | wind_speed_profile += fluctuations
197 |
198 | return wind_speed_profile
199 |
200 | def _filter(self, exc, wind_profile, window_size):
201 | """Filter the excitation signals with the AR filter coefficients"""
202 |
203 | hops = window_size // 2 # overlap
204 | hann_window = np.hanning(window_size) # hanning window
205 |
206 | profile = np.concatenate(
207 | (2 * np.ones(window_size), wind_profile, 2 * np.ones(window_size)))
208 |
209 | exc = np.concatenate((np.zeros(window_size), exc, np.zeros(window_size)))
210 | exc_length = len(exc)
211 |
212 | # Overlap-add approach for the time-varying filtering of the excitation signal
213 | num_windows = (exc_length - window_size) // hops + 1
214 | exc_filtered = np.zeros(exc_length)
215 |
216 | for time_frame in range(num_windows):
217 | start_idx = time_frame * hops
218 | end_idx = start_idx + window_size
219 | idx = np.arange(start_idx, end_idx)
220 |
221 | speed = np.clip(np.mean(profile[idx]), 2, 18)
222 | lpc = self._lsf2lpc(speed)
223 |
224 | exc_seg = exc[idx] * hann_window
225 | exc_seg_filtered = sp.signal.lfilter(
226 | np.array([1.0]), lpc, exc_seg)
227 |
228 | exc_filtered[idx] += exc_seg_filtered
229 |
230 | exc_filtered = exc_filtered[window_size:-window_size]
231 |
232 | return exc_filtered
233 |
234 | def _speed2par(self, speed):
235 | """Convert speed to GARCH parameters"""
236 |
237 | gp_alpha = np.array([-2.73244444508231e-05, 0.00141129711949206, -
238 | 0.0274652794467908, 0.257613241095714, -0.139824587447063])
239 | gp_beta = np.array(
240 | [-9.75160902595897e-05, 0.00464300106846736, -0.0871968755558256, 0.651013973757802])
241 | gp_omega = np.array(
242 | [9.69585296574741e-05, -0.00231853830578967, 0.0124681159197788])
243 |
244 | alpha = np.polyval(gp_alpha, speed)
245 | beta = np.polyval(gp_beta, speed)
246 | omega = np.polyval(gp_omega, speed)
247 |
248 | return alpha, beta, omega
249 |
250 | def _lsf2lpc(self, speed):
251 | """Generate LPC coefficients from the LSF-speed models given a speed value"""
252 |
253 | # Regression coefficients of the LFS-speed model
254 | # The n-th LFS coefficient corresponds to the n-th column
255 | regression_coeff = np.array([[-2.63412497797108e-06, 5.93162248595821e-05,
256 | 0.000215613938043173, -0.000149723789407121,
257 | -0.000213703084399375],
258 | [9.50240139044154e-05, -0.00271741166649528,
259 | -0.0103783584000284, 0.00483963669507075,
260 | 0.00931864887930701],
261 | [-0.000699199223507821, 0.0428714179385289,
262 | 0.177250839818556, -0.0329542145779793,
263 | -0.129910107562929],
264 | [0.0106849674771013, -0.234688122194936,
265 | -1.21337646113093, -0.168053225019258,
266 | 0.568371362156217],
267 | [-0.000966851130291645, 0.541693139684727,
268 | 3.24796925730457, 2.54984352038733,
269 | 1.86097523205089]])
270 | order = 5
271 |
272 | # Estimate LFS based on the speed value
273 | lfs_estimated = np.zeros(order)
274 |
275 | for order_idx in range(order):
276 | lfs_estimated[order_idx] = np.polyval(regression_coeff[:, order_idx], speed)
277 |
278 | # Convert LFS into LPC coefficients
279 | lpc_a = spectrum.lsf2poly(lfs_estimated)
280 |
281 | return lpc_a
282 |
283 | def plot_signals(self, wns, wind_profile):
284 | """
285 | Plot the generated wind noise signals and the associated wind profile.
286 |
287 | Example:
288 | wn = WindNoiseGenerator(fs=16000, duration=10)
289 | wn_sample, wind_profile = wn.generate_wind_noise()
290 | wn.plot_signals(wn_sample, wind_profile)
291 | """
292 |
293 | time_ind = np.arange(0, self.duration, 1. / self.fs)
294 | wns = wns[:time_ind.shape[0]]
295 |
296 | fig, axs = plt.subplots(3, 1, sharex=True, sharey=False)
297 | axs[0].plot(time_ind, wns)
298 | axs[0].set_ylabel('Amplitude')
299 | axs[0].grid(True)
300 | axs[0].autoscale(enable=True, axis='x', tight=True)
301 | axs[0].set_ylim(-1, 1)
302 |
303 | axs[2].plot(time_ind, np.abs(wind_profile), label='Wind Profile')
304 | axs[2].set_ylim(0, 6)
305 | axs[2].set_ylabel('Wind speed [m/s]')
306 | axs[2].grid(True)
307 | axs[2].axis('tight')
308 |
309 | axs[1].specgram(
310 | wns, Fs=self.fs, NFFT=512, noverlap=128,
311 | mode='magnitude', scale='dB', cmap='inferno',
312 | vmin=20 * np.log10(np.max(wns)) - 120
313 | )
314 | axs[1].autoscale(enable=True, axis='x', tight=True)
315 | axs[1].set_ylabel('Frequency [Hz]')
316 | axs[1].set_ylim(0, 8000)
317 | axs[1].yaxis.set_major_formatter(plt.FuncFormatter(lambda x, pos: f"{x/1e3:g}"))
318 |
319 | fig.tight_layout()
320 | plt.show()
321 |
322 | def play_signal(self, wns):
323 | """
324 | Play the generated wind noise.
325 |
326 | Example:
327 | wn = WindNoiseGenerator(fs=48000, duration=10)
328 | wn_sample = wn.generate_wind_noise()
329 | wn.play_signal(wn_sample)
330 | """
331 |
332 | sd.play(wns, self.fs)
333 | time.sleep(self.duration)
334 | sd.stop()
335 |
336 | def save_signal(self, wns, filename=None, print_log=False, num_ch=1, fs=48000):
337 | """
338 | Save the generated wind noise in a wave file.
339 |
340 | Example:
341 | wn = WindNoiseGenerator(Fs=16000, duration=10)
342 | wn_sample = wn.generate_wind_noise()
343 | wn.save_signal(wn_sample)
344 | """
345 |
346 | if filename is None:
347 | local_time = time.localtime(time.time())
348 | filename = f"""wind_noise_{fs // 1000}kHz_
349 | {local_time.tm_mday}{local_time.tm_mon}
350 | {local_time.tm_hour}{local_time.tm_min}{local_time.tm_sec}.wav"""
351 |
352 | if fs != 48000:
353 | wns = signal.resample(wns, int(self.duration * fs))
354 |
355 | if num_ch == 2:
356 | wns = np.array([wns, wns])
357 |
358 | sf.write(filename, wns.T, fs)
359 |
360 | if print_log:
361 | print(f'Audio file "{filename}" saved correctly in the working directory')
362 |
--------------------------------------------------------------------------------