├── 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 | --------------------------------------------------------------------------------