acoular 25.1__py3-none-any.whl → 25.3.post1__py3-none-any.whl
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.
- acoular/aiaa/aiaa.py +1 -1
- acoular/demo/acoular_demo.py +5 -5
- acoular/environments.py +458 -218
- acoular/fprocess.py +199 -97
- acoular/grids.py +714 -303
- acoular/microphones.py +157 -25
- acoular/process.py +405 -201
- acoular/signals.py +382 -87
- acoular/sources.py +1147 -286
- acoular/spectra.py +280 -128
- acoular/trajectory.py +119 -43
- acoular/version.py +2 -2
- {acoular-25.1.dist-info → acoular-25.3.post1.dist-info}/METADATA +6 -5
- {acoular-25.1.dist-info → acoular-25.3.post1.dist-info}/RECORD +17 -17
- {acoular-25.1.dist-info → acoular-25.3.post1.dist-info}/WHEEL +0 -0
- {acoular-25.1.dist-info → acoular-25.3.post1.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-25.1.dist-info → acoular-25.3.post1.dist-info}/licenses/LICENSE +0 -0
acoular/signals.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# ------------------------------------------------------------------------------
|
|
2
2
|
# Copyright (c) Acoular Development Team.
|
|
3
3
|
# ------------------------------------------------------------------------------
|
|
4
|
-
"""
|
|
4
|
+
"""
|
|
5
|
+
Implements signal generators for the simulation of acoustic sources.
|
|
5
6
|
|
|
6
7
|
.. autosummary::
|
|
7
8
|
:toctree: generated/
|
|
@@ -14,7 +15,6 @@
|
|
|
14
15
|
FiltWNoiseGenerator
|
|
15
16
|
SineGenerator
|
|
16
17
|
GenericSignalGenerator
|
|
17
|
-
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
# imports from other packages
|
|
@@ -45,45 +45,78 @@ from .internal import digest
|
|
|
45
45
|
|
|
46
46
|
@deprecated_alias({'numsamples': 'num_samples'})
|
|
47
47
|
class SignalGenerator(ABCHasStrictTraits):
|
|
48
|
-
"""
|
|
48
|
+
"""
|
|
49
|
+
ABC for a simple one-channel signal generator.
|
|
50
|
+
|
|
51
|
+
This ABC defines the common interface and attributes for all signal generator implementations.
|
|
52
|
+
It provides a template for generating one-channel signals with specified amplitude,
|
|
53
|
+
sampling frequency, and duration. Subclasses should implement the core functionality,
|
|
54
|
+
including signal generation and computation of the internal identifier.
|
|
55
|
+
|
|
56
|
+
See Also
|
|
57
|
+
--------
|
|
58
|
+
:func:`scipy.signal.resample` : Used for resampling signals in the :meth:`usignal` method.
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
should not be
|
|
60
|
+
Notes
|
|
61
|
+
-----
|
|
62
|
+
This class should not be instantiated directly. Instead, use a subclass that
|
|
63
|
+
implements the required methods for signal generation.
|
|
53
64
|
"""
|
|
54
65
|
|
|
55
|
-
#: Sampling frequency of the signal.
|
|
66
|
+
#: Sampling frequency of the signal in Hz. Default is ``1.0``.
|
|
56
67
|
sample_freq = Float(1.0, desc='sampling frequency')
|
|
57
68
|
|
|
58
|
-
#:
|
|
69
|
+
#: The number of samples to generate for the signal.
|
|
59
70
|
num_samples = CInt
|
|
60
71
|
|
|
61
|
-
|
|
72
|
+
#: A unique checksum identifier based on the object properties. (read-only)
|
|
62
73
|
digest = Property(depends_on=['sample_freq', 'num_samples'])
|
|
63
74
|
|
|
64
75
|
@abstractmethod
|
|
65
76
|
def _get_digest(self):
|
|
66
|
-
"""
|
|
77
|
+
"""Return the internal identifier."""
|
|
67
78
|
|
|
68
79
|
@abstractmethod
|
|
69
80
|
def signal(self):
|
|
70
|
-
"""
|
|
81
|
+
"""
|
|
82
|
+
Generate and return the signal.
|
|
83
|
+
|
|
84
|
+
This method must be implemented by subclasses to provide the generated signal
|
|
85
|
+
as a 1D array of samples.
|
|
86
|
+
"""
|
|
71
87
|
|
|
72
88
|
def usignal(self, factor):
|
|
73
|
-
"""
|
|
89
|
+
"""
|
|
90
|
+
Resample the signal at a higher sampling frequency.
|
|
74
91
|
|
|
75
|
-
|
|
92
|
+
This method uses Fourier transform-based resampling to deliver the signal at a
|
|
93
|
+
sampling frequency that is a multiple of the original :attr:`sample_freq`.
|
|
94
|
+
The resampled signal has a length of ``factor * num_samples``.
|
|
76
95
|
|
|
77
96
|
Parameters
|
|
78
97
|
----------
|
|
79
|
-
factor :
|
|
80
|
-
The factor
|
|
81
|
-
|
|
98
|
+
factor : int
|
|
99
|
+
The resampling factor. Defines how many times larger the new sampling frequency is
|
|
100
|
+
compared to the original :attr:`sample_freq`.
|
|
82
101
|
|
|
83
102
|
Returns
|
|
84
103
|
-------
|
|
85
|
-
|
|
86
|
-
The
|
|
104
|
+
:class:`numpy.ndarray`
|
|
105
|
+
The resampled signal as a 1D array of floats.
|
|
106
|
+
|
|
107
|
+
Notes
|
|
108
|
+
-----
|
|
109
|
+
This method relies on the :func:`scipy.signal.resample` function for resampling.
|
|
110
|
+
|
|
111
|
+
Examples
|
|
112
|
+
--------
|
|
113
|
+
Resample a signal by a factor of 4:
|
|
114
|
+
|
|
115
|
+
>>> from acoular import SineGenerator # Class extending SignalGenerator
|
|
116
|
+
>>> sg = SineGenerator(sample_freq=100.0, num_samples=1000)
|
|
117
|
+
>>> resampled_signal = sg.usignal(4)
|
|
118
|
+
>>> len(resampled_signal)
|
|
119
|
+
4000
|
|
87
120
|
"""
|
|
88
121
|
return resample(self.signal(), factor * self.num_samples)
|
|
89
122
|
|
|
@@ -92,27 +125,33 @@ class PeriodicSignalGenerator(SignalGenerator):
|
|
|
92
125
|
"""
|
|
93
126
|
Abstract base class for periodic signal generators.
|
|
94
127
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
128
|
+
The :class:`PeriodicSignalGenerator` class defines the common interface
|
|
129
|
+
for all :class:`SignalGenerator`-derived classes with periodic signals.
|
|
130
|
+
This class may be used as a base for class handling periodic signals
|
|
131
|
+
that can be characterized by their frequency, phase and amplitude.
|
|
132
|
+
|
|
133
|
+
It should not be used directly as it contains no real functionality.
|
|
134
|
+
|
|
135
|
+
See Also
|
|
136
|
+
--------
|
|
137
|
+
SineGenerator : Generate a sine signal.
|
|
99
138
|
"""
|
|
100
139
|
|
|
101
|
-
#:
|
|
140
|
+
#: The frequency of the signal. Default is ``1000.0``.
|
|
102
141
|
freq = Float(1000.0, desc='Frequency')
|
|
103
142
|
|
|
104
|
-
#:
|
|
143
|
+
#: The phase of the signal (in radians). Default is ``0.0``.
|
|
105
144
|
phase = Float(0.0, desc='Phase')
|
|
106
145
|
|
|
107
|
-
#:
|
|
146
|
+
#: The amplitude of the signal. Default is ``1.0``.
|
|
108
147
|
amplitude = Float(1.0)
|
|
109
148
|
|
|
110
|
-
|
|
149
|
+
#: Internal identifier based on generator properties. (read-only)
|
|
111
150
|
digest = Property(depends_on=['amplitude', 'num_samples', 'sample_freq', 'freq', 'phase'])
|
|
112
151
|
|
|
113
152
|
@abstractmethod
|
|
114
153
|
def _get_digest(self):
|
|
115
|
-
"""
|
|
154
|
+
"""Return the internal identifier."""
|
|
116
155
|
|
|
117
156
|
@abstractmethod
|
|
118
157
|
def signal(self):
|
|
@@ -120,35 +159,86 @@ class PeriodicSignalGenerator(SignalGenerator):
|
|
|
120
159
|
|
|
121
160
|
|
|
122
161
|
class NoiseGenerator(SignalGenerator):
|
|
123
|
-
"""
|
|
162
|
+
"""
|
|
163
|
+
Abstract base class for noise signal generators.
|
|
124
164
|
|
|
125
|
-
|
|
126
|
-
class may be used as a base for class handling noise signals
|
|
127
|
-
|
|
165
|
+
The :class:`NoiseGenerator` class defines the common interface for all :class:`SignalGenerator`
|
|
166
|
+
classes with noise signals. This class may be used as a base for class handling noise signals
|
|
167
|
+
that can be characterized by their RMS amplitude.
|
|
168
|
+
|
|
169
|
+
It should not be used directly as it contains no real functionality.
|
|
170
|
+
|
|
171
|
+
See Also
|
|
172
|
+
--------
|
|
173
|
+
:class:`acoular.signals.PNoiseGenerator` : For pink noise generation.
|
|
174
|
+
:class:`acoular.signals.WNoiseGenerator` : For pink white generation.
|
|
175
|
+
:class:`acoular.sources.UncorrelatedNoiseSource` : For per-channel noise generation.
|
|
128
176
|
"""
|
|
129
177
|
|
|
130
|
-
#: RMS amplitude of the signal.
|
|
178
|
+
#: Root mean square (RMS) amplitude of the signal. For a point source,
|
|
179
|
+
#: this corresponds to the RMS amplitude at a distance of 1 meter. Default is ``1.0``.
|
|
131
180
|
rms = Float(1.0, desc='rms amplitude')
|
|
132
181
|
|
|
133
|
-
#: Seed for random number generator
|
|
182
|
+
#: Seed for random number generator. Default is ``0``.
|
|
134
183
|
#: This parameter should be set differently for different instances
|
|
135
184
|
#: to guarantee statistically independent (non-correlated) outputs.
|
|
136
185
|
seed = Int(0, desc='random seed value')
|
|
137
186
|
|
|
138
|
-
|
|
187
|
+
#: Internal identifier based on generator properties. (read-only)
|
|
139
188
|
digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples'])
|
|
140
189
|
|
|
141
190
|
@abstractmethod
|
|
142
191
|
def _get_digest(self):
|
|
143
|
-
"""
|
|
192
|
+
"""Return the internal identifier."""
|
|
144
193
|
|
|
145
194
|
@abstractmethod
|
|
146
195
|
def signal(self):
|
|
147
|
-
"""
|
|
196
|
+
"""Generate and deliver the periodic signal."""
|
|
148
197
|
|
|
149
198
|
|
|
150
199
|
class WNoiseGenerator(NoiseGenerator):
|
|
151
|
-
"""
|
|
200
|
+
"""
|
|
201
|
+
White noise signal generator.
|
|
202
|
+
|
|
203
|
+
This class generates white noise signals with a specified
|
|
204
|
+
:attr:`root mean square (RMS)<SignalGenerator.rms>` amplitude,
|
|
205
|
+
:attr:`number of samples<SignalGenerator.num_samples>`, and
|
|
206
|
+
:attr:`sampling frequency<SignalGenerator.sample_freq>`. The white noise is generated using a
|
|
207
|
+
:obj:`random number generator<numpy.random.RandomState.standard_normal>` initialized with a
|
|
208
|
+
:attr:`user-defined seed<seed>` for reproducibility.
|
|
209
|
+
|
|
210
|
+
See Also
|
|
211
|
+
--------
|
|
212
|
+
:obj:`numpy.random.RandomState.standard_normal` :
|
|
213
|
+
Used here to generate normally distributed noise.
|
|
214
|
+
:class:`acoular.signals.PNoiseGenerator` : For pink noise generation.
|
|
215
|
+
:class:`acoular.sources.UncorrelatedNoiseSource` : For per-channel noise generation.
|
|
216
|
+
|
|
217
|
+
Examples
|
|
218
|
+
--------
|
|
219
|
+
Generate white noise with an RMS amplitude of 1.0 and 0.5:
|
|
220
|
+
|
|
221
|
+
>>> from acoular import WNoiseGenerator
|
|
222
|
+
>>> from numpy import mean
|
|
223
|
+
>>>
|
|
224
|
+
>>> # White noise with RMS of 1.0
|
|
225
|
+
>>> gen1 = WNoiseGenerator(rms=1.0, num_samples=1000, seed=42)
|
|
226
|
+
>>> signal1 = gen1.signal()
|
|
227
|
+
>>>
|
|
228
|
+
>>> # White noise with RMS of 0.5
|
|
229
|
+
>>> gen2 = WNoiseGenerator(rms=0.5, num_samples=1000, seed=24)
|
|
230
|
+
>>> signal2 = gen2.signal()
|
|
231
|
+
>>>
|
|
232
|
+
>>> mean(signal1) > mean(signal2)
|
|
233
|
+
np.True_
|
|
234
|
+
|
|
235
|
+
Ensure different outputs with different seeds:
|
|
236
|
+
|
|
237
|
+
>>> gen1 = WNoiseGenerator(num_samples=3, seed=42)
|
|
238
|
+
>>> gen2 = WNoiseGenerator(num_samples=3, seed=73)
|
|
239
|
+
>>> gen1.signal() == gen2.signal()
|
|
240
|
+
array([False, False, False])
|
|
241
|
+
"""
|
|
152
242
|
|
|
153
243
|
# internal identifier
|
|
154
244
|
digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples'])
|
|
@@ -158,35 +248,52 @@ class WNoiseGenerator(NoiseGenerator):
|
|
|
158
248
|
return digest(self)
|
|
159
249
|
|
|
160
250
|
def signal(self):
|
|
161
|
-
"""
|
|
251
|
+
"""
|
|
252
|
+
Generate and deliver the white noise signal.
|
|
253
|
+
|
|
254
|
+
The signal is created using a Gaussian distribution with mean 0 and variance 1,
|
|
255
|
+
scaled by the :attr:`RMS<SignalGenerator.rms>` amplitude of the object.
|
|
162
256
|
|
|
163
257
|
Returns
|
|
164
258
|
-------
|
|
165
|
-
|
|
166
|
-
|
|
259
|
+
:class:`numpy.ndarray`
|
|
260
|
+
A 1D array of floats containing the generated white noise signal.
|
|
261
|
+
The length of the array is equal to :attr:`~SignalGenerator.num_samples`.
|
|
167
262
|
"""
|
|
168
263
|
rnd_gen = RandomState(self.seed)
|
|
169
264
|
return self.rms * rnd_gen.standard_normal(self.num_samples)
|
|
170
265
|
|
|
171
266
|
|
|
172
267
|
class PNoiseGenerator(NoiseGenerator):
|
|
173
|
-
"""
|
|
268
|
+
"""
|
|
269
|
+
Generate pink noise signal.
|
|
174
270
|
|
|
175
|
-
|
|
176
|
-
|
|
271
|
+
The :class:`PNoiseGenerator` class generates pink noise signals,
|
|
272
|
+
which exhibit a :math:`1/f` power spectral density. Pink noise is characterized by
|
|
273
|
+
equal energy per octave, making it useful in various applications such as audio testing,
|
|
274
|
+
sound synthesis, and environmental noise simulations.
|
|
177
275
|
|
|
178
|
-
|
|
179
|
-
|
|
276
|
+
The pink noise simulation is based on the Voss-McCartney algorithm, which iteratively adds
|
|
277
|
+
noise with increasing wavelength to achieve the desired :math:`1/f` characteristic.
|
|
180
278
|
|
|
181
|
-
|
|
182
|
-
|
|
279
|
+
See Also
|
|
280
|
+
--------
|
|
281
|
+
:class:`acoular.signals.WNoiseGenerator` : For white noise generation.
|
|
282
|
+
:class:`acoular.sources.UncorrelatedNoiseSource` : For per-channel noise generation.
|
|
283
|
+
|
|
284
|
+
References
|
|
285
|
+
----------
|
|
286
|
+
- S.J. Orfanidis: Signal Processing (2010), pp. 729-733 :cite:`Orfanidis2010`
|
|
287
|
+
- Online discussion: http://www.firstpr.com.au/dsp/pink-noise/
|
|
183
288
|
"""
|
|
184
289
|
|
|
185
|
-
#: "Octave depth"
|
|
186
|
-
#:
|
|
290
|
+
#: "Octave depth" of the pink noise generation. Higher values result in a better approximation
|
|
291
|
+
#: of the :math:`1/f` spectrum at low frequencies but increase computation time. The maximum
|
|
292
|
+
#: allowable value depends on the :attr:`number of samples<SignalGenerator.num_samples>`.
|
|
293
|
+
#: Default is ``16``.
|
|
187
294
|
depth = Int(16, desc='octave depth')
|
|
188
295
|
|
|
189
|
-
|
|
296
|
+
#: A unique checksum identifier based on the object properties. (read-only)
|
|
190
297
|
digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples', 'depth'])
|
|
191
298
|
|
|
192
299
|
@cached_property
|
|
@@ -194,6 +301,27 @@ class PNoiseGenerator(NoiseGenerator):
|
|
|
194
301
|
return digest(self)
|
|
195
302
|
|
|
196
303
|
def signal(self):
|
|
304
|
+
"""
|
|
305
|
+
Generate and deliver the pink noise signal.
|
|
306
|
+
|
|
307
|
+
The signal is computed using the Voss-McCartney algorithm, which generates noise
|
|
308
|
+
with a :math:`1/f` power spectral density. The method ensures that the output has the
|
|
309
|
+
desired :attr:`RMS<SignalGenerator.rms>` amplitude and spectrum.
|
|
310
|
+
|
|
311
|
+
Returns
|
|
312
|
+
-------
|
|
313
|
+
:class:`numpy.ndarray`
|
|
314
|
+
A 1D array of floats containing the generated pink noise signal. The length
|
|
315
|
+
of the array is equal to :attr:`~SignalGenerator.num_samples`.
|
|
316
|
+
|
|
317
|
+
Notes
|
|
318
|
+
-----
|
|
319
|
+
- The "depth" parameter controls the number of octaves included in the pink noise
|
|
320
|
+
simulation. If the specified depth exceeds the maximum possible value based on
|
|
321
|
+
the number of samples, it is automatically adjusted, and a warning is printed.
|
|
322
|
+
- The output signal is scaled to have the same overall level as white noise by dividing
|
|
323
|
+
the result by ``sqrt(depth + 1.5)``.
|
|
324
|
+
"""
|
|
197
325
|
nums = self.num_samples
|
|
198
326
|
depth = self.depth
|
|
199
327
|
# maximum depth depending on number of samples
|
|
@@ -215,22 +343,69 @@ class PNoiseGenerator(NoiseGenerator):
|
|
|
215
343
|
|
|
216
344
|
|
|
217
345
|
class FiltWNoiseGenerator(WNoiseGenerator):
|
|
218
|
-
"""
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
346
|
+
"""
|
|
347
|
+
Generate filtered white noise using an **AR**, **MA**, or **ARMA** process.
|
|
348
|
+
|
|
349
|
+
- **AR:** autoregressive (:attr:`ar`)
|
|
350
|
+
- **MA:** moving-average (:attr:`ma`)
|
|
351
|
+
- **ARMA:** autoregressive moving-average
|
|
352
|
+
|
|
353
|
+
This class extends the :class:`WNoiseGenerator` class to apply a digital filter to white noise,
|
|
354
|
+
producing a signal with specific characteristics based on the provided filter coefficients.
|
|
355
|
+
The desired filter is defined using the autoregressive coefficients (:attr:`ar`) and
|
|
356
|
+
moving-average coefficients (:attr:`ma`).
|
|
357
|
+
|
|
358
|
+
The filter is implemented as a series of second-order sections (sos) for numerical stability,
|
|
359
|
+
especially at high filter orders.
|
|
360
|
+
|
|
361
|
+
See Also
|
|
362
|
+
--------
|
|
363
|
+
:class:`WNoiseGenerator` : For white noise generation.
|
|
364
|
+
|
|
365
|
+
Notes
|
|
366
|
+
-----
|
|
367
|
+
- The output signal is adjusted for the group delay introduced by the filter, ensuring
|
|
368
|
+
proper alignment of the filtered signal.
|
|
369
|
+
- The RMS value specified in the :attr:`~NoiseGenerator.rms` attribute corresponds to the
|
|
370
|
+
original white noise signal and not the filtered output.
|
|
371
|
+
|
|
372
|
+
Examples
|
|
373
|
+
--------
|
|
374
|
+
Generate filtered white noise using :attr:`AR<ar>` and :attr:`MA<ma>` coefficients:
|
|
375
|
+
|
|
376
|
+
>>> import acoular as ac
|
|
377
|
+
>>> import numpy as np
|
|
378
|
+
>>>
|
|
379
|
+
>>> # Define AR and MA coefficients
|
|
380
|
+
>>> ar = np.array([1.0, -0.5])
|
|
381
|
+
>>> ma = np.array([0.5, 0.5])
|
|
382
|
+
>>>
|
|
383
|
+
>>> # Create generator
|
|
384
|
+
>>> gen = ac.FiltWNoiseGenerator(
|
|
385
|
+
... rms=1.0,
|
|
386
|
+
... seed=42,
|
|
387
|
+
... sample_freq=1000,
|
|
388
|
+
... num_samples=10000,
|
|
389
|
+
... ar=ar,
|
|
390
|
+
... ma=ma,
|
|
391
|
+
... )
|
|
392
|
+
>>>
|
|
393
|
+
>>> # Generate signal
|
|
394
|
+
>>> signal = gen.signal()
|
|
395
|
+
>>> print(signal[:10]) # Print the first 10 samples
|
|
396
|
+
[0.24835708 0.30340346 0.40641385 1.28856612 1.2887213 0.41021549
|
|
397
|
+
0.87764567 1.61214661 0.95505348 0.51406957]
|
|
227
398
|
"""
|
|
228
399
|
|
|
400
|
+
#: A :class:`numpy.ndarray` of autoregressive coefficients (denominator). Default is ``[]``,
|
|
401
|
+
#: which results in no AR filtering (i.e., all-pole filter is ``[1.0]``).
|
|
229
402
|
ar = CArray(value=array([]), dtype=float, desc='autoregressive coefficients (coefficients of the denominator)')
|
|
230
403
|
|
|
404
|
+
#: A :class:`numpy.ndarray` of moving-average coefficients (numerator). Default is ``[]``,
|
|
405
|
+
#: which results in no MA filtering (i.e., all-zero filter is ``[1.0]``).
|
|
231
406
|
ma = CArray(value=array([]), dtype=float, desc='moving-average coefficients (coefficients of the numerator)')
|
|
232
407
|
|
|
233
|
-
|
|
408
|
+
#: A unique checksum identifier based on the object properties. (read-only)
|
|
234
409
|
digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples', 'ar', 'ma'])
|
|
235
410
|
|
|
236
411
|
@cached_property
|
|
@@ -238,17 +413,42 @@ class FiltWNoiseGenerator(WNoiseGenerator):
|
|
|
238
413
|
return digest(self)
|
|
239
414
|
|
|
240
415
|
def handle_empty_coefficients(self, coefficients):
|
|
416
|
+
"""
|
|
417
|
+
Handle empty filter coefficient arrays by returning a default value.
|
|
418
|
+
|
|
419
|
+
This method ensures that both the autoregressive (:attr:`ar`) and moving-average
|
|
420
|
+
(:attr:`ma`) coefficients are non-empty before filtering. If a coefficient array is empty,
|
|
421
|
+
it is replaced with a default array containing a single value of ``1.0``.
|
|
422
|
+
|
|
423
|
+
Parameters
|
|
424
|
+
----------
|
|
425
|
+
coefficients : :class:`numpy.ndarray`
|
|
426
|
+
Array of filter coefficients to check.
|
|
427
|
+
|
|
428
|
+
Returns
|
|
429
|
+
-------
|
|
430
|
+
:class:`numpy.ndarray`
|
|
431
|
+
The original array if it is non-empty, or a default array containing ``[1.0]``
|
|
432
|
+
if the input array is empty.
|
|
433
|
+
"""
|
|
241
434
|
if coefficients.size == 0:
|
|
242
435
|
return array([1.0])
|
|
243
436
|
return coefficients
|
|
244
437
|
|
|
245
438
|
def signal(self):
|
|
246
|
-
"""
|
|
439
|
+
"""
|
|
440
|
+
Generate and return the filtered white noise signal.
|
|
441
|
+
|
|
442
|
+
This method creates a white noise signal with the specified
|
|
443
|
+
:attr:`RMS value<NoiseGenerator.rms>` and :attr:`~NoiseGenerator.seed`, then filters it
|
|
444
|
+
using the autoregressive (:attr:`ar`) and moving-average (:attr:`ma`) coefficients.
|
|
445
|
+
The filtering process compensates for group delay introduced by the filter.
|
|
247
446
|
|
|
248
447
|
Returns
|
|
249
448
|
-------
|
|
250
|
-
|
|
251
|
-
|
|
449
|
+
:class:`numpy.ndarray` of :class:`floats<float>`
|
|
450
|
+
An array representing the filtered white noise signal. The length of the returned array
|
|
451
|
+
is equal to :attr:`the number of samples<SignalGenerator.num_samples>`.
|
|
252
452
|
"""
|
|
253
453
|
rnd_gen = RandomState(self.seed)
|
|
254
454
|
ma = self.handle_empty_coefficients(self.ma)
|
|
@@ -263,9 +463,68 @@ class FiltWNoiseGenerator(WNoiseGenerator):
|
|
|
263
463
|
|
|
264
464
|
|
|
265
465
|
class SineGenerator(PeriodicSignalGenerator):
|
|
266
|
-
"""
|
|
466
|
+
r"""
|
|
467
|
+
Generate a sine signal.
|
|
267
468
|
|
|
268
|
-
|
|
469
|
+
The :class:`SineGenerator` class extends the :class:`PeriodicSignalGenerator` class and
|
|
470
|
+
generates a sinusoidal signal based on specified :attr:`~PeriodicSignalGenerator.amplitude`,
|
|
471
|
+
:attr:`frequency<PeriodicSignalGenerator.freq>`, and :attr:`~PeriodicSignalGenerator.phase`.
|
|
472
|
+
|
|
473
|
+
This generator is commonly used for creating test signals in signal processing, acoustics,
|
|
474
|
+
and control systems.
|
|
475
|
+
|
|
476
|
+
The signal is defined as
|
|
477
|
+
|
|
478
|
+
.. math::
|
|
479
|
+
|
|
480
|
+
s(t) = A \sin(2 \pi f t + \phi)
|
|
481
|
+
|
|
482
|
+
where:
|
|
483
|
+
- :math:`A` is the amplitude,
|
|
484
|
+
- :math:`f` is the frequency,
|
|
485
|
+
- :math:`\phi` is the phase,
|
|
486
|
+
- :math:`t` is the time (computed from the
|
|
487
|
+
:attr:`sampling frequency<SignalGenerator.sample_freq>` and the
|
|
488
|
+
:attr:`number of samples<SignalGenerator.num_samples>`).
|
|
489
|
+
|
|
490
|
+
See Also
|
|
491
|
+
--------
|
|
492
|
+
PeriodicSignalGenerator : Base class for periodic signal generators.
|
|
493
|
+
|
|
494
|
+
Examples
|
|
495
|
+
--------
|
|
496
|
+
Generate a sine wave signal:
|
|
497
|
+
|
|
498
|
+
>>> import acoular as ac
|
|
499
|
+
>>>
|
|
500
|
+
>>> gen = ac.SineGenerator(
|
|
501
|
+
... amplitude=2.0,
|
|
502
|
+
... freq=50.0,
|
|
503
|
+
... phase=0.0,
|
|
504
|
+
... num_samples=1000,
|
|
505
|
+
... sample_freq=1000,
|
|
506
|
+
... )
|
|
507
|
+
>>> signal = gen.signal()
|
|
508
|
+
>>> signal[:5] # The first 5 samples
|
|
509
|
+
array([0. , 0.61803399, 1.1755705 , 1.61803399, 1.90211303])
|
|
510
|
+
|
|
511
|
+
Generate a sine wave with a phase shift (arguably a cosine wave):
|
|
512
|
+
|
|
513
|
+
>>> import numpy as np
|
|
514
|
+
>>>
|
|
515
|
+
>>> gen = ac.SineGenerator(
|
|
516
|
+
... amplitude=1.0,
|
|
517
|
+
... freq=100.0,
|
|
518
|
+
... phase=np.pi / 2,
|
|
519
|
+
... num_samples=500,
|
|
520
|
+
... sample_freq=2000,
|
|
521
|
+
... )
|
|
522
|
+
>>> signal = gen.signal()
|
|
523
|
+
>>> signal[:5] # The first 5 samples
|
|
524
|
+
array([1. , 0.95105652, 0.80901699, 0.58778525, 0.30901699])
|
|
525
|
+
"""
|
|
526
|
+
|
|
527
|
+
#: A unique checksum identifier based on the object properties. (read-only)
|
|
269
528
|
digest = Property(depends_on=['num_samples', 'sample_freq', 'amplitude', 'freq', 'phase'])
|
|
270
529
|
|
|
271
530
|
@cached_property
|
|
@@ -273,12 +532,25 @@ class SineGenerator(PeriodicSignalGenerator):
|
|
|
273
532
|
return digest(self)
|
|
274
533
|
|
|
275
534
|
def signal(self):
|
|
276
|
-
"""
|
|
535
|
+
r"""
|
|
536
|
+
Generate and return the sine wave signal.
|
|
537
|
+
|
|
538
|
+
The method computes the sine wave based on the specified
|
|
539
|
+
:attr:`~PeriodicSignalGenerator.amplitude`, :attr:`frequency<PeriodicSignalGenerator.freq>`,
|
|
540
|
+
and :attr:`~PeriodicSignalGenerator.phase`. The time values are determined by the
|
|
541
|
+
:attr:`sampling frequency<SignalGenerator.sample_freq>` and the
|
|
542
|
+
:attr:`number of samples<SignalGenerator.num_samples>`.
|
|
277
543
|
|
|
278
544
|
Returns
|
|
279
545
|
-------
|
|
280
|
-
|
|
281
|
-
|
|
546
|
+
:class:`numpy.ndarray` of :class:`floats<float>`
|
|
547
|
+
A 1D array representing the sine wave signal.
|
|
548
|
+
The length of the array is equal to :attr:`~SignalGenerator.num_samples`.
|
|
549
|
+
|
|
550
|
+
Notes
|
|
551
|
+
-----
|
|
552
|
+
The generator supports high-frequency and high-resolution signals,
|
|
553
|
+
limited by the Nyquist criterion.
|
|
282
554
|
"""
|
|
283
555
|
t = arange(self.num_samples, dtype=float) / self.sample_freq
|
|
284
556
|
return self.amplitude * sin(2 * pi * self.freq * t + self.phase)
|
|
@@ -286,33 +558,51 @@ class SineGenerator(PeriodicSignalGenerator):
|
|
|
286
558
|
|
|
287
559
|
@deprecated_alias({'rms': 'amplitude'})
|
|
288
560
|
class GenericSignalGenerator(SignalGenerator):
|
|
289
|
-
"""
|
|
561
|
+
"""
|
|
562
|
+
Generate signals from a :class:`~acoular.base.SamplesGenerator` or derived object.
|
|
563
|
+
|
|
564
|
+
The :class:`GenericSignalGenerator` class enables the integration of arbitrary signals into
|
|
565
|
+
Acoular processing chains. The signal is fetched from a specified data source and optionally
|
|
566
|
+
scaled by an amplitude factor. It supports looping the signal to match the desired number of
|
|
567
|
+
samples and can handle signals with multiple channels (only the first channel is used).
|
|
290
568
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
569
|
+
Common use cases include:
|
|
570
|
+
- Injecting custom or pre-recorded signals from HDF5 files.
|
|
571
|
+
- Creating signals using the :class:`~acoular.sources.TimeSamples` class.
|
|
572
|
+
- Generating a continuous or repeated signal for simulations.
|
|
294
573
|
|
|
295
|
-
|
|
296
|
-
|
|
574
|
+
Notes
|
|
575
|
+
-----
|
|
576
|
+
If the signal source has more than one channel, only channel 0 is used.
|
|
577
|
+
|
|
578
|
+
Examples
|
|
579
|
+
--------
|
|
580
|
+
Inject a random signal into a processing chain:
|
|
581
|
+
|
|
582
|
+
>>> import acoular as ac
|
|
297
583
|
>>> import numpy as np
|
|
298
|
-
>>>
|
|
584
|
+
>>>
|
|
299
585
|
>>> data = np.random.rand(1000, 1)
|
|
300
|
-
>>> ts = TimeSamples(data=data, sample_freq=51200)
|
|
301
|
-
>>> sig = GenericSignalGenerator(source=ts)
|
|
586
|
+
>>> ts = ac.TimeSamples(data=data, sample_freq=51200)
|
|
587
|
+
>>> sig = ac.GenericSignalGenerator(source=ts)
|
|
588
|
+
>>> output_signal = sig.signal()
|
|
302
589
|
"""
|
|
303
590
|
|
|
304
|
-
#:
|
|
591
|
+
#: The data source from which the signal is fetched.
|
|
592
|
+
#: This can be any object derived from :class:`SamplesGenerator`.
|
|
305
593
|
source = Instance(SamplesGenerator)
|
|
306
594
|
|
|
307
|
-
#:
|
|
595
|
+
#: Scaling factor applied to the generated signal. Defaults to ``1.0``.
|
|
308
596
|
amplitude = Float(1.0)
|
|
309
597
|
|
|
310
|
-
#: Sampling frequency of output signal, as
|
|
598
|
+
#: Sampling frequency of the output signal, as provided by the :attr:`source` object.
|
|
311
599
|
sample_freq = Delegate('source')
|
|
312
600
|
|
|
313
601
|
_num_samples = CInt(0)
|
|
314
602
|
|
|
315
|
-
#:
|
|
603
|
+
#: The number of samples to generate. Default is the number of samples available in the
|
|
604
|
+
#: :attr:`source` (``source.num_samples``). If set explicitly, it can exceed the source length,
|
|
605
|
+
#: in which case the signal will loop if :attr:`loop_signal` is ``True``.
|
|
316
606
|
num_samples = Property()
|
|
317
607
|
|
|
318
608
|
def _get_num_samples(self):
|
|
@@ -323,11 +613,11 @@ class GenericSignalGenerator(SignalGenerator):
|
|
|
323
613
|
def _set_num_samples(self, num_samples):
|
|
324
614
|
self._num_samples = num_samples
|
|
325
615
|
|
|
326
|
-
#:
|
|
327
|
-
#:
|
|
616
|
+
#: If ``True`` (default), the signal is repeated to meet the requested :attr:`num_samples`.
|
|
617
|
+
#: If ``False``, the signal stops once the source data is exhausted.
|
|
328
618
|
loop_signal = Bool(True)
|
|
329
619
|
|
|
330
|
-
|
|
620
|
+
#: A unique checksum identifier based on the object properties. (read-only)
|
|
331
621
|
digest = Property(
|
|
332
622
|
depends_on=['source.digest', 'loop_signal', 'num_samples', 'amplitude'],
|
|
333
623
|
)
|
|
@@ -337,13 +627,18 @@ class GenericSignalGenerator(SignalGenerator):
|
|
|
337
627
|
return digest(self)
|
|
338
628
|
|
|
339
629
|
def signal(self):
|
|
340
|
-
"""
|
|
630
|
+
"""
|
|
631
|
+
Deliver the signal from the specified source.
|
|
341
632
|
|
|
342
633
|
Returns
|
|
343
634
|
-------
|
|
344
|
-
array of floats
|
|
345
|
-
The resulting signal
|
|
635
|
+
:class:`numpy.array` of :class:`floats<float>`
|
|
636
|
+
The resulting signal, scaled by the :attr:`amplitude` attribute, with a length
|
|
637
|
+
matching :attr:`~GenericSignalGenerator.num_samples`.
|
|
346
638
|
|
|
639
|
+
Warnings
|
|
640
|
+
--------
|
|
641
|
+
A warning is raised if the source has more than one channel.
|
|
347
642
|
"""
|
|
348
643
|
block = 1024
|
|
349
644
|
if self.source.num_channels > 1:
|