acoular 24.10__py3-none-any.whl → 25.3__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/signals.py CHANGED
@@ -1,149 +1,335 @@
1
1
  # ------------------------------------------------------------------------------
2
2
  # Copyright (c) Acoular Development Team.
3
3
  # ------------------------------------------------------------------------------
4
- """Implements signal generators for the simulation of acoustic sources.
4
+ """
5
+ Implements signal generators for the simulation of acoustic sources.
5
6
 
6
7
  .. autosummary::
7
8
  :toctree: generated/
8
9
 
9
10
  SignalGenerator
11
+ PeriodicSignalGenerator
12
+ NoiseGenerator
10
13
  WNoiseGenerator
11
14
  PNoiseGenerator
12
15
  FiltWNoiseGenerator
13
16
  SineGenerator
14
17
  GenericSignalGenerator
15
-
16
18
  """
17
19
 
18
20
  # imports from other packages
21
+ from abc import abstractmethod
19
22
  from warnings import warn
20
23
 
21
24
  from numpy import arange, array, log, pi, repeat, sin, sqrt, tile, zeros
22
25
  from numpy.random import RandomState
23
26
  from scipy.signal import resample, sosfilt, tf2sos
24
- from traits.api import Bool, CArray, CLong, Delegate, Float, HasPrivateTraits, Int, Property, Trait, cached_property
27
+ from traits.api import (
28
+ ABCHasStrictTraits,
29
+ Bool,
30
+ CArray,
31
+ CInt,
32
+ Delegate,
33
+ Float,
34
+ Instance,
35
+ Int,
36
+ Property,
37
+ cached_property,
38
+ )
25
39
 
26
40
  # acoular imports
27
41
  from .base import SamplesGenerator
42
+ from .deprecation import deprecated_alias
28
43
  from .internal import digest
29
44
 
30
45
 
31
- class SignalGenerator(HasPrivateTraits):
32
- """Virtual base class for a simple one-channel signal generator.
33
-
34
- Defines the common interface for all SignalGenerator classes. This class
35
- may be used as a base for specialized SignalGenerator implementations. It
36
- should not be used directly as it contains no real functionality.
46
+ @deprecated_alias({'numsamples': 'num_samples'})
47
+ class SignalGenerator(ABCHasStrictTraits):
37
48
  """
49
+ ABC for a simple one-channel signal generator.
38
50
 
39
- #: RMS amplitude of source signal (for point source: in 1 m distance).
40
- rms = Float(1.0, desc='rms amplitude')
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.
59
+
60
+ Notes
61
+ -----
62
+ This class should not be instantiated directly. Instead, use a subclass that
63
+ implements the required methods for signal generation.
64
+ """
41
65
 
42
- #: Sampling frequency of the signal.
66
+ #: Sampling frequency of the signal in Hz. Default is ``1.0``.
43
67
  sample_freq = Float(1.0, desc='sampling frequency')
44
68
 
45
- #: Number of samples to generate.
46
- numsamples = CLong
69
+ #: The number of samples to generate for the signal.
70
+ num_samples = CInt
47
71
 
48
- # internal identifier
49
- digest = Property
72
+ #: A unique checksum identifier based on the object properties. (read-only)
73
+ digest = Property(depends_on=['sample_freq', 'num_samples'])
50
74
 
75
+ @abstractmethod
51
76
  def _get_digest(self):
52
- return ''
77
+ """Return the internal identifier."""
53
78
 
79
+ @abstractmethod
54
80
  def signal(self):
55
- """Deliver the signal."""
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
+ """
56
87
 
57
88
  def usignal(self, factor):
58
- """Delivers the signal resampled with a multiple of the sampling freq.
89
+ """
90
+ Resample the signal at a higher sampling frequency.
59
91
 
60
- Uses fourier transform method for resampling (from scipy.signal).
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``.
61
95
 
62
96
  Parameters
63
97
  ----------
64
- factor : integer
65
- The factor defines how many times the new sampling frequency is
66
- larger than :attr:`sample_freq`.
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`.
67
101
 
68
102
  Returns
69
103
  -------
70
- array of floats
71
- The resulting signal of length `factor` * :attr:`numsamples`.
72
-
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
73
120
  """
74
- return resample(self.signal(), factor * self.numsamples)
121
+ return resample(self.signal(), factor * self.num_samples)
122
+
123
+
124
+ class PeriodicSignalGenerator(SignalGenerator):
125
+ """
126
+ Abstract base class for periodic signal generators.
127
+
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.
138
+ """
139
+
140
+ #: The frequency of the signal. Default is ``1000.0``.
141
+ freq = Float(1000.0, desc='Frequency')
142
+
143
+ #: The phase of the signal (in radians). Default is ``0.0``.
144
+ phase = Float(0.0, desc='Phase')
145
+
146
+ #: The amplitude of the signal. Default is ``1.0``.
147
+ amplitude = Float(1.0)
148
+
149
+ #: Internal identifier based on generator properties. (read-only)
150
+ digest = Property(depends_on=['amplitude', 'num_samples', 'sample_freq', 'freq', 'phase'])
151
+
152
+ @abstractmethod
153
+ def _get_digest(self):
154
+ """Return the internal identifier."""
155
+
156
+ @abstractmethod
157
+ def signal(self):
158
+ """Deliver the signal."""
159
+
160
+
161
+ class NoiseGenerator(SignalGenerator):
162
+ """
163
+ Abstract base class for noise signal generators.
75
164
 
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.
76
168
 
77
- class WNoiseGenerator(SignalGenerator):
78
- """White noise signal generator."""
169
+ It should not be used directly as it contains no real functionality.
79
170
 
80
- #: Seed for random number generator, defaults to 0.
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.
176
+ """
177
+
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``.
180
+ rms = Float(1.0, desc='rms amplitude')
181
+
182
+ #: Seed for random number generator. Default is ``0``.
81
183
  #: This parameter should be set differently for different instances
82
184
  #: to guarantee statistically independent (non-correlated) outputs.
83
185
  seed = Int(0, desc='random seed value')
84
186
 
187
+ #: Internal identifier based on generator properties. (read-only)
188
+ digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples'])
189
+
190
+ @abstractmethod
191
+ def _get_digest(self):
192
+ """Return the internal identifier."""
193
+
194
+ @abstractmethod
195
+ def signal(self):
196
+ """Generate and deliver the periodic signal."""
197
+
198
+
199
+ class WNoiseGenerator(NoiseGenerator):
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
+ """
242
+
85
243
  # internal identifier
86
- digest = Property(
87
- depends_on=['rms', 'numsamples', 'sample_freq', 'seed', '__class__'],
88
- )
244
+ digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples'])
89
245
 
90
246
  @cached_property
91
247
  def _get_digest(self):
92
248
  return digest(self)
93
249
 
94
250
  def signal(self):
95
- """Deliver the signal.
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.
96
256
 
97
257
  Returns
98
258
  -------
99
- Array of floats
100
- The resulting signal as an array of length :attr:`~SignalGenerator.numsamples`.
101
-
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`.
102
262
  """
103
263
  rnd_gen = RandomState(self.seed)
104
- return self.rms * rnd_gen.standard_normal(self.numsamples)
264
+ return self.rms * rnd_gen.standard_normal(self.num_samples)
105
265
 
106
266
 
107
- class PNoiseGenerator(SignalGenerator):
108
- """Pink noise signal generator.
267
+ class PNoiseGenerator(NoiseGenerator):
268
+ """
269
+ Generate pink noise signal.
109
270
 
110
- Simulation of pink noise is based on the Voss-McCartney algorithm.
111
- Ref.:
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.
112
275
 
113
- * S.J. Orfanidis: Signal Processing (2010), pp. 729-733
114
- * online discussion: http://www.firstpr.com.au/dsp/pink-noise/
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.
115
278
 
116
- The idea is to iteratively add larger-wavelength noise to get 1/f
117
- characteristic.
118
- """
279
+ See Also
280
+ --------
281
+ :class:`acoular.signals.WNoiseGenerator` : For white noise generation.
282
+ :class:`acoular.sources.UncorrelatedNoiseSource` : For per-channel noise generation.
119
283
 
120
- #: Seed for random number generator, defaults to 0.
121
- #: This parameter should be set differently for different instances
122
- #: to guarantee statistically independent (non-correlated) outputs.
123
- seed = Int(0, desc='random seed value')
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/
288
+ """
124
289
 
125
- #: "Octave depth" -- higher values for 1/f spectrum at low frequencies,
126
- #: but longer calculation, defaults to 16.
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``.
127
294
  depth = Int(16, desc='octave depth')
128
295
 
129
- # internal identifier
130
- digest = Property(
131
- depends_on=['rms', 'numsamples', 'sample_freq', 'seed', 'depth', '__class__'],
132
- )
296
+ #: A unique checksum identifier based on the object properties. (read-only)
297
+ digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples', 'depth'])
133
298
 
134
299
  @cached_property
135
300
  def _get_digest(self):
136
301
  return digest(self)
137
302
 
138
303
  def signal(self):
139
- nums = self.numsamples
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
+ """
325
+ nums = self.num_samples
140
326
  depth = self.depth
141
327
  # maximum depth depending on number of samples
142
328
  max_depth = int(log(nums) / log(2))
143
329
 
144
330
  if depth > max_depth:
145
331
  depth = max_depth
146
- print('Pink noise filter depth set to maximum possible value of %d.' % max_depth)
332
+ print(f'Pink noise filter depth set to maximum possible value of {max_depth:d}.')
147
333
 
148
334
  rnd_gen = RandomState(self.seed)
149
335
  s = rnd_gen.standard_normal(nums)
@@ -157,51 +343,112 @@ class PNoiseGenerator(SignalGenerator):
157
343
 
158
344
 
159
345
  class FiltWNoiseGenerator(WNoiseGenerator):
160
- """Filtered white noise signal following an autoregressive (AR), moving-average
161
- (MA) or autoregressive moving-average (ARMA) process.
162
-
163
- The desired frequency response of the filter can be defined by specifying
164
- the filter coefficients :attr:`ar` and :attr:`ma`.
165
- The RMS value specified via the :attr:`rms` attribute belongs to the white noise
166
- signal and differs from the RMS value of the filtered signal.
167
- For numerical stability at high orders, the filter is a combination of second order
168
- sections (sos).
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]
169
398
  """
170
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]``).
171
402
  ar = CArray(value=array([]), dtype=float, desc='autoregressive coefficients (coefficients of the denominator)')
172
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]``).
173
406
  ma = CArray(value=array([]), dtype=float, desc='moving-average coefficients (coefficients of the numerator)')
174
407
 
175
- # internal identifier
176
- digest = Property(
177
- depends_on=[
178
- 'ar',
179
- 'ma',
180
- 'rms',
181
- 'numsamples',
182
- 'sample_freq',
183
- 'seed',
184
- '__class__',
185
- ],
186
- )
408
+ #: A unique checksum identifier based on the object properties. (read-only)
409
+ digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples', 'ar', 'ma'])
187
410
 
188
411
  @cached_property
189
412
  def _get_digest(self):
190
413
  return digest(self)
191
414
 
192
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
+ """
193
434
  if coefficients.size == 0:
194
435
  return array([1.0])
195
436
  return coefficients
196
437
 
197
438
  def signal(self):
198
- """Deliver the signal.
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.
199
446
 
200
447
  Returns
201
448
  -------
202
- Array of floats
203
- The resulting signal as an array of length :attr:`~SignalGenerator.numsamples`.
204
-
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>`.
205
452
  """
206
453
  rnd_gen = RandomState(self.seed)
207
454
  ma = self.handle_empty_coefficients(self.ma)
@@ -210,117 +457,169 @@ class FiltWNoiseGenerator(WNoiseGenerator):
210
457
  ntaps = ma.shape[0]
211
458
  sdelay = round(0.5 * (ntaps - 1))
212
459
  wnoise = self.rms * rnd_gen.standard_normal(
213
- self.numsamples + sdelay,
460
+ self.num_samples + sdelay,
214
461
  ) # create longer signal to compensate delay
215
462
  return sosfilt(sos, x=wnoise)[sdelay:]
216
463
 
217
464
 
218
- class SineGenerator(SignalGenerator):
219
- """Sine signal generator with adjustable frequency and phase."""
465
+ class SineGenerator(PeriodicSignalGenerator):
466
+ r"""
467
+ Generate a sine signal.
220
468
 
221
- #: Sine wave frequency, float, defaults to 1000.0.
222
- freq = Float(1000.0, desc='Frequency')
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`.
223
472
 
224
- #: Sine wave phase (in radians), float, defaults to 0.0.
225
- phase = Float(0.0, desc='Phase')
473
+ This generator is commonly used for creating test signals in signal processing, acoustics,
474
+ and control systems.
226
475
 
227
- # Internal shadow trait for rms/amplitude values.
228
- # Do not set directly.
229
- _amp = Float(1.0)
476
+ The signal is defined as
230
477
 
231
- #: RMS of source signal (for point source: in 1 m distance).
232
- #: Deprecated. For amplitude use :attr:`amplitude`.
233
- rms = Property(desc='rms amplitude')
478
+ .. math::
234
479
 
235
- def _get_rms(self):
236
- return self._amp / 2**0.5
480
+ s(t) = A \sin(2 \pi f t + \phi)
237
481
 
238
- def _set_rms(self, rms):
239
- warn(
240
- 'Using rms to set amplitude is deprecated and will be removed in version 25.01. '
241
- 'Up to Acoular 20.02, rms is interpreted as sine amplitude. '
242
- 'This has since been corrected (rms now is 1/sqrt(2) of amplitude). '
243
- "Use 'amplitude' trait to directly set the ampltiude.",
244
- DeprecationWarning,
245
- stacklevel=2,
246
- )
247
- self._amp = rms * 2**0.5
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>`).
248
489
 
249
- #: Amplitude of source signal (for point source: in 1 m distance).
250
- #: Defaults to 1.0.
251
- amplitude = Property(desc='amplitude')
490
+ See Also
491
+ --------
492
+ PeriodicSignalGenerator : Base class for periodic signal generators.
252
493
 
253
- def _get_amplitude(self):
254
- return self._amp
494
+ Examples
495
+ --------
496
+ Generate a sine wave signal:
255
497
 
256
- def _set_amplitude(self, amp):
257
- self._amp = amp
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])
258
510
 
259
- # internal identifier
260
- digest = Property(
261
- depends_on=['_amp', 'numsamples', 'sample_freq', 'freq', 'phase', '__class__'],
262
- )
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)
528
+ digest = Property(depends_on=['num_samples', 'sample_freq', 'amplitude', 'freq', 'phase'])
263
529
 
264
530
  @cached_property
265
531
  def _get_digest(self):
266
532
  return digest(self)
267
533
 
268
534
  def signal(self):
269
- """Deliver the signal.
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>`.
270
543
 
271
544
  Returns
272
545
  -------
273
- array of floats
274
- The resulting signal as an array of length :attr:`~SignalGenerator.numsamples`.
275
-
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.
276
554
  """
277
- t = arange(self.numsamples, dtype=float) / self.sample_freq
555
+ t = arange(self.num_samples, dtype=float) / self.sample_freq
278
556
  return self.amplitude * sin(2 * pi * self.freq * t + self.phase)
279
557
 
280
558
 
559
+ @deprecated_alias({'rms': 'amplitude'})
281
560
  class GenericSignalGenerator(SignalGenerator):
282
- """Generate signal from output of :class:`~acoular.base.SamplesGenerator` object.
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).
283
568
 
284
- This class can be used to inject arbitrary signals into Acoular processing
285
- chains. For example, it can be used to read signals from a HDF5 file or create any signal
286
- by using the :class:`acoular.sources.TimeSamples` class.
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.
287
573
 
288
- Example
289
- -------
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
290
583
  >>> import numpy as np
291
- >>> from acoular import TimeSamples, GenericSignalGenerator
584
+ >>>
292
585
  >>> data = np.random.rand(1000, 1)
293
- >>> ts = TimeSamples(data=data, sample_freq=51200)
294
- >>> sig = GenericSignalGenerator(source=ts)
295
-
586
+ >>> ts = ac.TimeSamples(data=data, sample_freq=51200)
587
+ >>> sig = ac.GenericSignalGenerator(source=ts)
588
+ >>> output_signal = sig.signal()
296
589
  """
297
590
 
298
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
299
- source = Trait(SamplesGenerator)
591
+ #: The data source from which the signal is fetched.
592
+ #: This can be any object derived from :class:`SamplesGenerator`.
593
+ source = Instance(SamplesGenerator)
594
+
595
+ #: Scaling factor applied to the generated signal. Defaults to ``1.0``.
596
+ amplitude = Float(1.0)
300
597
 
301
- #: Sampling frequency of output signal, as given by :attr:`source`.
598
+ #: Sampling frequency of the output signal, as provided by the :attr:`source` object.
302
599
  sample_freq = Delegate('source')
303
600
 
304
- _numsamples = CLong(0)
601
+ _num_samples = CInt(0)
305
602
 
306
- #: Number of samples to generate. Is set to source.numsamples by default.
307
- numsamples = Property()
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``.
606
+ num_samples = Property()
308
607
 
309
- def _get_numsamples(self):
310
- if self._numsamples:
311
- return self._numsamples
312
- return self.source.numsamples
608
+ def _get_num_samples(self):
609
+ if self._num_samples:
610
+ return self._num_samples
611
+ return self.source.num_samples
313
612
 
314
- def _set_numsamples(self, numsamples):
315
- self._numsamples = numsamples
613
+ def _set_num_samples(self, num_samples):
614
+ self._num_samples = num_samples
316
615
 
317
- #: Boolean flag, if 'True' (default), signal track is repeated if requested
318
- #: :attr:`numsamples` is higher than available sample number
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.
319
618
  loop_signal = Bool(True)
320
619
 
321
- # internal identifier
620
+ #: A unique checksum identifier based on the object properties. (read-only)
322
621
  digest = Property(
323
- depends_on=['source.digest', 'loop_signal', 'numsamples', 'rms', '__class__'],
622
+ depends_on=['source.digest', 'loop_signal', 'num_samples', 'amplitude'],
324
623
  )
325
624
 
326
625
  @cached_property
@@ -328,22 +627,27 @@ class GenericSignalGenerator(SignalGenerator):
328
627
  return digest(self)
329
628
 
330
629
  def signal(self):
331
- """Deliver the signal.
630
+ """
631
+ Deliver the signal from the specified source.
332
632
 
333
633
  Returns
334
634
  -------
335
- array of floats
336
- The resulting signal as an array of length :attr:`~GenericSignalGenerator.numsamples`.
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`.
337
638
 
639
+ Warnings
640
+ --------
641
+ A warning is raised if the source has more than one channel.
338
642
  """
339
643
  block = 1024
340
- if self.source.numchannels > 1:
644
+ if self.source.num_channels > 1:
341
645
  warn(
342
646
  'Signal source has more than one channel. Only channel 0 will be used for signal.',
343
647
  Warning,
344
648
  stacklevel=2,
345
649
  )
346
- nums = self.numsamples
650
+ nums = self.num_samples
347
651
  track = zeros(nums)
348
652
 
349
653
  # iterate through source generator to fill signal track
@@ -366,6 +670,4 @@ class GenericSignalGenerator(SignalGenerator):
366
670
  res = nums % stop # last part of unfinished loop
367
671
  if res > 0:
368
672
  track[stop * nloops :] = track[:res]
369
-
370
- # The rms value is just an amplification here
371
- return self.rms * track
673
+ return self.amplitude * track