tuney 0.2.0__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.
tuney/audio/scipy.py ADDED
@@ -0,0 +1,701 @@
1
+ # From scipy
2
+ # Author: Travis Oliphant
3
+ # 2003
4
+ #
5
+ # Feb. 2010: Updated by Warren Weckesser:
6
+ # Rewrote much of chirp()
7
+ # Added sweep_poly()
8
+ import numpy as np
9
+ from numpy import (
10
+ asarray,
11
+ cos,
12
+ exp,
13
+ extract,
14
+ log,
15
+ mod,
16
+ nan,
17
+ pi,
18
+ place,
19
+ polyint,
20
+ polyval,
21
+ sin,
22
+ sqrt,
23
+ zeros,
24
+ )
25
+
26
+ __all__ = ['sawtooth', 'square', 'gausspulse', 'chirp', 'sweep_poly', 'unit_impulse']
27
+
28
+
29
+ def sawtooth(t, width=1):
30
+ """
31
+ Return a periodic sawtooth or triangle waveform.
32
+
33
+ The sawtooth waveform has a period ``2*pi``, rises from -1 to 1 on the
34
+ interval 0 to ``width*2*pi``, then drops from 1 to -1 on the interval
35
+ ``width*2*pi`` to ``2*pi``. `width` must be in the interval [0, 1].
36
+
37
+ Note that this is not band-limited. It produces an infinite number
38
+ of harmonics, which are aliased back and forth across the frequency
39
+ spectrum.
40
+
41
+ Parameters
42
+ ----------
43
+ t : array_like
44
+ Time.
45
+ width : array_like, optional
46
+ Width of the rising ramp as a proportion of the total cycle.
47
+ Default is 1, producing a rising ramp, while 0 produces a falling
48
+ ramp. `width` = 0.5 produces a triangle wave.
49
+ If an array, causes wave shape to change over time, and must be the
50
+ same length as t.
51
+
52
+ Returns
53
+ -------
54
+ y : ndarray
55
+ Output array containing the sawtooth waveform.
56
+
57
+ Examples
58
+ --------
59
+ A 5 Hz waveform sampled at 500 Hz for 1 second:
60
+
61
+ >>> import numpy as np
62
+ >>> from scipy import signal
63
+ >>> import matplotlib.pyplot as plt
64
+ >>> t = np.linspace(0, 1, 500)
65
+ >>> plt.plot(t, signal.sawtooth(2 * np.pi * 5 * t))
66
+
67
+ """
68
+ t, w = asarray(t), asarray(width)
69
+ w = asarray(w + (t - t))
70
+ t = asarray(t + (w - w))
71
+ y = zeros(t.shape, dtype='d')
72
+
73
+ # width must be between 0 and 1 inclusive
74
+ mask1 = (w > 1) | (w < 0)
75
+ place(y, mask1, nan)
76
+
77
+ # take t modulo 2*pi
78
+ tmod = mod(t, 2 * pi)
79
+
80
+ # on the interval 0 to width*2*pi function is
81
+ # tmod / (pi*w) - 1
82
+ mask2 = (1 - mask1) & (tmod < w * 2 * pi)
83
+ tsub = extract(mask2, tmod)
84
+ wsub = extract(mask2, w)
85
+ place(y, mask2, tsub / (pi * wsub) - 1)
86
+
87
+ # on the interval width*2*pi to 2*pi function is
88
+ # (pi*(w+1)-tmod) / (pi*(1-w))
89
+
90
+ mask3 = (1 - mask1) & (1 - mask2)
91
+ tsub = extract(mask3, tmod)
92
+ wsub = extract(mask3, w)
93
+ place(y, mask3, (pi * (wsub + 1) - tsub) / (pi * (1 - wsub)))
94
+ return y
95
+
96
+
97
+ def square(t, duty=0.5):
98
+ """
99
+ Return a periodic square-wave waveform.
100
+
101
+ The square wave has a period ``2*pi``, has value +1 from 0 to
102
+ ``2*pi*duty`` and -1 from ``2*pi*duty`` to ``2*pi``. `duty` must be in
103
+ the interval [0,1].
104
+
105
+ Note that this is not band-limited. It produces an infinite number
106
+ of harmonics, which are aliased back and forth across the frequency
107
+ spectrum.
108
+
109
+ Parameters
110
+ ----------
111
+ t : array_like
112
+ The input time array.
113
+ duty : array_like, optional
114
+ Duty cycle. Default is 0.5 (50% duty cycle).
115
+ If an array, causes wave shape to change over time, and must be the
116
+ same length as t.
117
+
118
+ Returns
119
+ -------
120
+ y : ndarray
121
+ Output array containing the square waveform.
122
+
123
+ Examples
124
+ --------
125
+ A 5 Hz waveform sampled at 500 Hz for 1 second:
126
+
127
+ >>> import numpy as np
128
+ >>> from scipy import signal
129
+ >>> import matplotlib.pyplot as plt
130
+ >>> t = np.linspace(0, 1, 500, endpoint=False)
131
+ >>> plt.plot(t, signal.square(2 * np.pi * 5 * t))
132
+ >>> plt.ylim(-2, 2)
133
+
134
+ A pulse-width modulated sine wave:
135
+
136
+ >>> plt.figure()
137
+ >>> sig = np.sin(2 * np.pi * t)
138
+ >>> pwm = signal.square(2 * np.pi * 30 * t, duty=(sig + 1)/2)
139
+ >>> plt.subplot(2, 1, 1)
140
+ >>> plt.plot(t, sig)
141
+ >>> plt.subplot(2, 1, 2)
142
+ >>> plt.plot(t, pwm)
143
+ >>> plt.ylim(-1.5, 1.5)
144
+
145
+ """
146
+ t, w = asarray(t), asarray(duty)
147
+ w = asarray(w + (t - t))
148
+ t = asarray(t + (w - w))
149
+ y = zeros(t.shape, dtype='d')
150
+
151
+ # width must be between 0 and 1 inclusive
152
+ mask1 = (w > 1) | (w < 0)
153
+ place(y, mask1, nan)
154
+
155
+ # on the interval 0 to duty*2*pi function is 1
156
+ tmod = mod(t, 2 * pi)
157
+ mask2 = (1 - mask1) & (tmod < w * 2 * pi)
158
+ place(y, mask2, 1)
159
+
160
+ # on the interval duty*2*pi to 2*pi function is
161
+ # (pi*(w+1)-tmod) / (pi*(1-w))
162
+ mask3 = (1 - mask1) & (1 - mask2)
163
+ place(y, mask3, -1)
164
+ return y
165
+
166
+
167
+ def gausspulse(t, fc=1000, bw=0.5, bwr=-6, tpr=-60, retquad=False, retenv=False):
168
+ """
169
+ Return a Gaussian modulated sinusoid:
170
+
171
+ ``exp(-a t^2) exp(1j*2*pi*fc*t).``
172
+
173
+ If `retquad` is True, then return the real and imaginary parts
174
+ (in-phase and quadrature).
175
+ If `retenv` is True, then return the envelope (unmodulated signal).
176
+ Otherwise, return the real part of the modulated sinusoid.
177
+
178
+ Parameters
179
+ ----------
180
+ t : ndarray or the string 'cutoff'
181
+ Input array.
182
+ fc : float, optional
183
+ Center frequency (e.g. Hz). Default is 1000.
184
+ bw : float, optional
185
+ Fractional bandwidth in frequency domain of pulse (e.g. Hz).
186
+ Default is 0.5.
187
+ bwr : float, optional
188
+ Reference level at which fractional bandwidth is calculated (dB).
189
+ Default is -6.
190
+ tpr : float, optional
191
+ If `t` is 'cutoff', then the function returns the cutoff
192
+ time for when the pulse amplitude falls below `tpr` (in dB).
193
+ Default is -60.
194
+ retquad : bool, optional
195
+ If True, return the quadrature (imaginary) as well as the real part
196
+ of the signal. Default is False.
197
+ retenv : bool, optional
198
+ If True, return the envelope of the signal. Default is False.
199
+
200
+ Returns
201
+ -------
202
+ yI : ndarray
203
+ Real part of signal. Always returned.
204
+ yQ : ndarray
205
+ Imaginary part of signal. Only returned if `retquad` is True.
206
+ yenv : ndarray
207
+ Envelope of signal. Only returned if `retenv` is True.
208
+
209
+ Examples
210
+ --------
211
+ Plot real component, imaginary component, and envelope for a 5 Hz pulse,
212
+ sampled at 100 Hz for 2 seconds:
213
+
214
+ >>> import numpy as np
215
+ >>> from scipy import signal
216
+ >>> import matplotlib.pyplot as plt
217
+ >>> t = np.linspace(-1, 1, 2 * 100, endpoint=False)
218
+ >>> i, q, e = signal.gausspulse(t, fc=5, retquad=True, retenv=True)
219
+ >>> plt.plot(t, i, t, q, t, e, '--')
220
+
221
+ """
222
+ if fc < 0:
223
+ raise ValueError(f'Center frequency (fc={fc:.2f}) must be >=0.')
224
+ if bw <= 0:
225
+ raise ValueError(f'Fractional bandwidth (bw={bw:.2f}) must be > 0.')
226
+ if bwr >= 0:
227
+ raise ValueError(
228
+ f'Reference level for bandwidth (bwr={bwr:.2f}) must be < 0 dB'
229
+ )
230
+
231
+ # exp(-a t^2) <-> sqrt(pi/a) exp(-pi^2/a * f^2) = g(f)
232
+
233
+ ref = pow(10.0, bwr / 20.0)
234
+ # fdel = fc*bw/2: g(fdel) = ref --- solve this for a
235
+ #
236
+ # pi^2/a * fc^2 * bw^2 /4=-log(ref)
237
+ a = -((pi * fc * bw) ** 2) / (4.0 * log(ref))
238
+
239
+ if isinstance(t, str):
240
+ if t == 'cutoff': # compute cut_off point
241
+ # Solve exp(-a tc**2) = tref for tc
242
+ # tc = sqrt(-log(tref) / a) where tref = 10^(tpr/20)
243
+ if tpr >= 0:
244
+ raise ValueError('Reference level for time cutoff must be < 0 dB')
245
+ tref = pow(10.0, tpr / 20.0)
246
+ return sqrt(-log(tref) / a)
247
+ else:
248
+ raise ValueError("If `t` is a string, it must be 'cutoff'")
249
+
250
+ yenv = exp(-a * t * t)
251
+ yI = yenv * cos(2 * pi * fc * t)
252
+ yQ = yenv * sin(2 * pi * fc * t)
253
+ if not retquad and not retenv:
254
+ return yI
255
+ if not retquad and retenv:
256
+ return yI, yenv
257
+ if retquad and not retenv:
258
+ return yI, yQ
259
+ if retquad and retenv:
260
+ return yI, yQ, yenv
261
+
262
+
263
+ def chirp(t, f0, t1, f1, method='linear', phi=0, vertex_zero=True, *, complex=False):
264
+ r"""Frequency-swept cosine generator.
265
+
266
+ In the following, 'Hz' should be interpreted as 'cycles per unit';
267
+ there is no requirement here that the unit is one second. The
268
+ important distinction is that the units of rotation are cycles, not
269
+ radians. Likewise, `t` could be a measurement of space instead of time.
270
+
271
+ Parameters
272
+ ----------
273
+ t : array_like
274
+ Times at which to evaluate the waveform.
275
+ f0 : float
276
+ Frequency (e.g. Hz) at time t=0.
277
+ t1 : float
278
+ Time at which `f1` is specified.
279
+ f1 : float
280
+ Frequency (e.g. Hz) of the waveform at time `t1`.
281
+ method : {'linear', 'quadratic', 'logarithmic', 'hyperbolic'}, optional
282
+ Kind of frequency sweep. If not given, `linear` is assumed. See
283
+ Notes below for more details.
284
+ phi : float, optional
285
+ Phase offset, in degrees. Default is 0.
286
+ vertex_zero : bool, optional
287
+ This parameter is only used when `method` is 'quadratic'.
288
+ It determines whether the vertex of the parabola that is the graph
289
+ of the frequency is at t=0 or t=t1.
290
+ complex : bool, optional
291
+ This parameter creates a complex-valued analytic signal instead of a
292
+ real-valued signal. It allows the use of complex baseband (in communications
293
+ domain). Default is False.
294
+
295
+ .. versionadded:: 1.15.0
296
+
297
+ Returns
298
+ -------
299
+ y : ndarray
300
+ A numpy array containing the signal evaluated at `t` with the requested
301
+ time-varying frequency. More precisely, the function returns
302
+ ``exp(1j*phase + 1j*(pi/180)*phi) if complex else cos(phase + (pi/180)*phi)``
303
+ where `phase` is the integral (from 0 to `t`) of ``2*pi*f(t)``.
304
+ The instantaneous frequency ``f(t)`` is defined below.
305
+
306
+ See Also
307
+ --------
308
+ sweep_poly
309
+
310
+ Notes
311
+ -----
312
+ There are four possible options for the parameter `method`, which have a (long)
313
+ standard form and some allowed abbreviations. The formulas for the instantaneous
314
+ frequency :math:`f(t)` of the generated signal are as follows:
315
+
316
+ 1. Parameter `method` in ``('linear', 'lin', 'li')``:
317
+
318
+ .. math::
319
+ f(t) = f_0 + \beta\, t \quad\text{with}\quad
320
+ \beta = \frac{f_1 - f_0}{t_1}
321
+
322
+ Frequency :math:`f(t)` varies linearly over time with a constant rate
323
+ :math:`\beta`.
324
+
325
+ 2. Parameter `method` in ``('quadratic', 'quad', 'q')``:
326
+
327
+ .. math::
328
+ f(t) =
329
+ \begin{cases}
330
+ f_0 + \beta\, t^2 & \text{if vertex_zero is True,}\\
331
+ f_1 + \beta\, (t_1 - t)^2 & \text{otherwise,}
332
+ \end{cases}
333
+ \quad\text{with}\quad
334
+ \beta = \frac{f_1 - f_0}{t_1^2}
335
+
336
+ The graph of the frequency f(t) is a parabola through :math:`(0, f_0)` and
337
+ :math:`(t_1, f_1)`. By default, the vertex of the parabola is at
338
+ :math:`(0, f_0)`. If `vertex_zero` is ``False``, then the vertex is at
339
+ :math:`(t_1, f_1)`.
340
+ To use a more general quadratic function, or an arbitrary
341
+ polynomial, use the function `scipy.signal.sweep_poly`.
342
+
343
+ 3. Parameter `method` in ``('logarithmic', 'log', 'lo')``:
344
+
345
+ .. math::
346
+ f(t) = f_0 \left(\frac{f_1}{f_0}\right)^{t/t_1}
347
+
348
+ :math:`f_0` and :math:`f_1` must be nonzero and have the same sign.
349
+ This signal is also known as a geometric or exponential chirp.
350
+
351
+ 4. Parameter `method` in ``('hyperbolic', 'hyp')``:
352
+
353
+ .. math::
354
+ f(t) = \frac{\alpha}{\beta\, t + \gamma} \quad\text{with}\quad
355
+ \alpha = f_0 f_1 t_1, \ \beta = f_0 - f_1, \ \gamma = f_1 t_1
356
+
357
+ :math:`f_0` and :math:`f_1` must be nonzero.
358
+
359
+
360
+ Examples
361
+ --------
362
+ For the first example, a linear chirp ranging from 6 Hz to 1 Hz over 10 seconds is
363
+ plotted:
364
+
365
+ >>> import numpy as np
366
+ >>> from matplotlib.pyplot import tight_layout
367
+ >>> from scipy.signal import chirp, square, ShortTimeFFT
368
+ >>> from scipy.signal.windows import gaussian
369
+ >>> import matplotlib.pyplot as plt
370
+ ...
371
+ >>> N, T = 1000, 0.01 # number of samples and sampling interval for 10 s signal
372
+ >>> t = np.arange(N) * T # timestamps
373
+ ...
374
+ >>> x_lin = chirp(t, f0=6, f1=1, t1=10, method='linear')
375
+ ...
376
+ >>> fg0, ax0 = plt.subplots()
377
+ >>> ax0.set_title(r"Linear Chirp from $f(0)=6\,$Hz to $f(10)=1\,$Hz")
378
+ >>> ax0.set(xlabel="Time $t$ in Seconds", ylabel=r"Amplitude $x_\text{lin}(t)$")
379
+ >>> ax0.plot(t, x_lin)
380
+ >>> plt.show()
381
+
382
+ The following four plots each show the short-time Fourier transform of a chirp
383
+ ranging from 45 Hz to 5 Hz with different values for the parameter `method`
384
+ (and `vertex_zero`):
385
+
386
+ >>> x_qu0 = chirp(t, f0=45, f1=5, t1=N*T, method='quadratic', vertex_zero=True)
387
+ >>> x_qu1 = chirp(t, f0=45, f1=5, t1=N*T, method='quadratic', vertex_zero=False)
388
+ >>> x_log = chirp(t, f0=45, f1=5, t1=N*T, method='logarithmic')
389
+ >>> x_hyp = chirp(t, f0=45, f1=5, t1=N*T, method='hyperbolic')
390
+ ...
391
+ >>> win = gaussian(50, std=12, sym=True)
392
+ >>> SFT = ShortTimeFFT(win, hop=2, fs=1/T, mfft=800, scale_to='magnitude')
393
+ >>> ts = ("'quadratic', vertex_zero=True", "'quadratic', vertex_zero=False",
394
+ ... "'logarithmic'", "'hyperbolic'")
395
+ >>> fg1, ax1s = plt.subplots(2, 2, sharex='all', sharey='all',
396
+ ... figsize=(6, 5), layout="constrained")
397
+ >>> for x_, ax_, t_ in zip([x_qu0, x_qu1, x_log, x_hyp], ax1s.ravel(), ts):
398
+ ... aSx = abs(SFT.stft(x_))
399
+ ... im_ = ax_.imshow(aSx, origin='lower', aspect='auto', extent=SFT.extent(N),
400
+ ... cmap='plasma')
401
+ ... ax_.set_title(t_)
402
+ ... if t_ == "'hyperbolic'":
403
+ ... fg1.colorbar(im_, ax=ax1s, label='Magnitude $|S_z(t,f)|$')
404
+ >>> _ = fg1.supxlabel("Time $t$ in Seconds") # `_ =` is needed to pass doctests
405
+ >>> _ = fg1.supylabel("Frequency $f$ in Hertz")
406
+ >>> plt.show()
407
+
408
+ Finally, the short-time Fourier transform of a complex-valued linear chirp
409
+ ranging from -30 Hz to 30 Hz is depicted:
410
+
411
+ >>> z_lin = chirp(t, f0=-30, f1=30, t1=N*T, method="linear", complex=True)
412
+ >>> SFT.fft_mode = 'centered' # needed to work with complex signals
413
+ >>> aSz = abs(SFT.stft(z_lin))
414
+ ...
415
+ >>> fg2, ax2 = plt.subplots()
416
+ >>> ax2.set_title(r"Linear Chirp from $-30\,$Hz to $30\,$Hz")
417
+ >>> ax2.set(xlabel="Time $t$ in Seconds", ylabel="Frequency $f$ in Hertz")
418
+ >>> im2 = ax2.imshow(aSz, origin='lower', aspect='auto',
419
+ ... extent=SFT.extent(N), cmap='viridis')
420
+ >>> fg2.colorbar(im2, label='Magnitude $|S_z(t,f)|$')
421
+ >>> plt.show()
422
+
423
+ Note that using negative frequencies makes only sense with complex-valued signals.
424
+ Furthermore, the magnitude of the complex exponential function is one whereas the
425
+ magnitude of the real-valued cosine function is only 1/2.
426
+ """
427
+ # 'phase' is computed in _chirp_phase, to make testing easier.
428
+ phase = _chirp_phase(t, f0, t1, f1, method, vertex_zero) + np.deg2rad(phi)
429
+ return np.exp(1j * phase) if complex else np.cos(phase)
430
+
431
+
432
+ def _chirp_phase(t, f0, t1, f1, method='linear', vertex_zero=True):
433
+ """
434
+ Calculate the phase used by `chirp` to generate its output.
435
+
436
+ See `chirp` for a description of the arguments.
437
+
438
+ """
439
+ t = asarray(t)
440
+ f0 = float(f0)
441
+ t1 = float(t1)
442
+ f1 = float(f1)
443
+ if method in ['linear', 'lin', 'li']:
444
+ beta = (f1 - f0) / t1
445
+ phase = 2 * pi * (f0 * t + 0.5 * beta * t * t)
446
+
447
+ elif method in ['quadratic', 'quad', 'q']:
448
+ beta = (f1 - f0) / (t1**2)
449
+ if vertex_zero:
450
+ phase = 2 * pi * (f0 * t + beta * t**3 / 3)
451
+ else:
452
+ phase = 2 * pi * (f1 * t + beta * ((t1 - t) ** 3 - t1**3) / 3)
453
+
454
+ elif method in ['logarithmic', 'log', 'lo']:
455
+ if f0 * f1 <= 0.0:
456
+ raise ValueError(
457
+ 'For a logarithmic chirp, f0 and f1 must be '
458
+ 'nonzero and have the same sign.'
459
+ )
460
+ if f0 == f1:
461
+ phase = 2 * pi * f0 * t
462
+ else:
463
+ beta = t1 / log(f1 / f0)
464
+ phase = 2 * pi * beta * f0 * (pow(f1 / f0, t / t1) - 1.0)
465
+
466
+ elif method in ['hyperbolic', 'hyp']:
467
+ if f0 == 0 or f1 == 0:
468
+ raise ValueError('For a hyperbolic chirp, f0 and f1 must be nonzero.')
469
+ if f0 == f1:
470
+ # Degenerate case: constant frequency.
471
+ phase = 2 * pi * f0 * t
472
+ else:
473
+ # Singular point: the instantaneous frequency blows up
474
+ # when t == sing.
475
+ sing = -f1 * t1 / (f0 - f1)
476
+ phase = 2 * pi * (-sing * f0) * log(np.abs(1 - t / sing))
477
+
478
+ else:
479
+ raise ValueError(
480
+ "method must be 'linear', 'quadratic', 'logarithmic', "
481
+ f"or 'hyperbolic', but a value of {method!r} was given."
482
+ )
483
+
484
+ return phase
485
+
486
+
487
+ def sweep_poly(t, poly, phi=0):
488
+ """
489
+ Frequency-swept cosine generator, with a time-dependent frequency.
490
+
491
+ This function generates a sinusoidal function whose instantaneous
492
+ frequency varies with time. The frequency at time `t` is given by
493
+ the polynomial `poly`.
494
+
495
+ Parameters
496
+ ----------
497
+ t : ndarray
498
+ Times at which to evaluate the waveform.
499
+ poly : 1-D array_like or instance of numpy.poly1d
500
+ The desired frequency expressed as a polynomial. If `poly` is
501
+ a list or ndarray of length n, then the elements of `poly` are
502
+ the coefficients of the polynomial, and the instantaneous
503
+ frequency is
504
+
505
+ ``f(t) = poly[0]*t**(n-1) + poly[1]*t**(n-2) + ... + poly[n-1]``
506
+
507
+ If `poly` is an instance of numpy.poly1d, then the
508
+ instantaneous frequency is
509
+
510
+ ``f(t) = poly(t)``
511
+
512
+ phi : float, optional
513
+ Phase offset, in degrees, Default: 0.
514
+
515
+ Returns
516
+ -------
517
+ sweep_poly : ndarray
518
+ A numpy array containing the signal evaluated at `t` with the
519
+ requested time-varying frequency. More precisely, the function
520
+ returns ``cos(phase + (pi/180)*phi)``, where `phase` is the integral
521
+ (from 0 to t) of ``2 * pi * f(t)``; ``f(t)`` is defined above.
522
+
523
+ See Also
524
+ --------
525
+ chirp
526
+
527
+ Notes
528
+ -----
529
+ .. versionadded:: 0.8.0
530
+
531
+ If `poly` is a list or ndarray of length `n`, then the elements of
532
+ `poly` are the coefficients of the polynomial, and the instantaneous
533
+ frequency is:
534
+
535
+ ``f(t) = poly[0]*t**(n-1) + poly[1]*t**(n-2) + ... + poly[n-1]``
536
+
537
+ If `poly` is an instance of `numpy.poly1d`, then the instantaneous
538
+ frequency is:
539
+
540
+ ``f(t) = poly(t)``
541
+
542
+ Finally, the output `s` is:
543
+
544
+ ``cos(phase + (pi/180)*phi)``
545
+
546
+ where `phase` is the integral from 0 to `t` of ``2 * pi * f(t)``,
547
+ ``f(t)`` as defined above.
548
+
549
+ Examples
550
+ --------
551
+ Compute the waveform with instantaneous frequency::
552
+
553
+ f(t) = 0.025*t**3 - 0.36*t**2 + 1.25*t + 2
554
+
555
+ over the interval 0 <= t <= 10.
556
+
557
+ >>> import numpy as np
558
+ >>> from scipy.signal import sweep_poly
559
+ >>> p = np.poly1d([0.025, -0.36, 1.25, 2.0])
560
+ >>> t = np.linspace(0, 10, 5001)
561
+ >>> w = sweep_poly(t, p)
562
+
563
+ Plot it:
564
+
565
+ >>> import matplotlib.pyplot as plt
566
+ >>> plt.subplot(2, 1, 1)
567
+ >>> plt.plot(t, w)
568
+ >>> plt.title("Sweep Poly\\nwith frequency " +
569
+ ... "$f(t) = 0.025t^3 - 0.36t^2 + 1.25t + 2$")
570
+ >>> plt.subplot(2, 1, 2)
571
+ >>> plt.plot(t, p(t), 'r', label='f(t)')
572
+ >>> plt.legend()
573
+ >>> plt.xlabel('t')
574
+ >>> plt.tight_layout()
575
+ >>> plt.show()
576
+
577
+ """
578
+ # 'phase' is computed in _sweep_poly_phase, to make testing easier.
579
+ phase = _sweep_poly_phase(t, poly)
580
+ # Convert to radians.
581
+ phi *= pi / 180
582
+ return cos(phase + phi)
583
+
584
+
585
+ def _sweep_poly_phase(t, poly):
586
+ """
587
+ Calculate the phase used by sweep_poly to generate its output.
588
+
589
+ See `sweep_poly` for a description of the arguments.
590
+
591
+ """
592
+ # polyint handles lists, ndarrays and instances of poly1d automatically.
593
+ intpoly = polyint(poly)
594
+ phase = 2 * pi * polyval(intpoly, t)
595
+ return phase
596
+
597
+
598
+ def unit_impulse(shape, idx=None, dtype=float):
599
+ r"""
600
+ Unit impulse signal (discrete delta function) or unit basis vector.
601
+
602
+ Parameters
603
+ ----------
604
+ shape : int or tuple of int
605
+ Number of samples in the output (1-D), or a tuple that represents the
606
+ shape of the output (N-D).
607
+ idx : None or int or tuple of int or 'mid', optional
608
+ Index at which the value is 1. If None, defaults to the 0th element.
609
+ If ``idx='mid'``, the impulse will be centered at ``shape // 2`` in
610
+ all dimensions. If an int, the impulse will be at `idx` in all
611
+ dimensions.
612
+ dtype : data-type, optional
613
+ The desired data-type for the array, e.g., ``numpy.int8``. Default is
614
+ ``numpy.float64``.
615
+
616
+ Returns
617
+ -------
618
+ y : ndarray
619
+ Output array containing an impulse signal.
620
+
621
+ Notes
622
+ -----
623
+ In digital signal processing literature the unit impulse signal is often
624
+ represented by the Kronecker delta. [1]_ I.e., a signal :math:`u_k[n]`,
625
+ which is zero everywhere except being one at the :math:`k`-th sample,
626
+ can be expressed as
627
+
628
+ .. math::
629
+
630
+ u_k[n] = \delta[n-k] \equiv \delta_{n,k}\ .
631
+
632
+ Furthermore, the unit impulse is frequently interpreted as the discrete-time
633
+ version of the continuous-time Dirac distribution. [2]_
634
+
635
+ References
636
+ ----------
637
+ .. [1] "Kronecker delta", *Wikipedia*,
638
+ https://en.wikipedia.org/wiki/Kronecker_delta#Digital_signal_processing
639
+ .. [2] "Dirac delta function" *Wikipedia*,
640
+ https://en.wikipedia.org/wiki/Dirac_delta_function#Relationship_to_the_Kronecker_delta
641
+
642
+ .. versionadded:: 0.19.0
643
+
644
+ Examples
645
+ --------
646
+ An impulse at the 0th element (:math:`\\delta[n]`):
647
+
648
+ >>> from scipy import signal
649
+ >>> signal.unit_impulse(8)
650
+ array([ 1., 0., 0., 0., 0., 0., 0., 0.])
651
+
652
+ Impulse offset by 2 samples (:math:`\\delta[n-2]`):
653
+
654
+ >>> signal.unit_impulse(7, 2)
655
+ array([ 0., 0., 1., 0., 0., 0., 0.])
656
+
657
+ 2-dimensional impulse, centered:
658
+
659
+ >>> signal.unit_impulse((3, 3), 'mid')
660
+ array([[ 0., 0., 0.],
661
+ [ 0., 1., 0.],
662
+ [ 0., 0., 0.]])
663
+
664
+ Impulse at (2, 2), using broadcasting:
665
+
666
+ >>> signal.unit_impulse((4, 4), 2)
667
+ array([[ 0., 0., 0., 0.],
668
+ [ 0., 0., 0., 0.],
669
+ [ 0., 0., 1., 0.],
670
+ [ 0., 0., 0., 0.]])
671
+
672
+ Plot the impulse response of a 4th-order Butterworth lowpass filter:
673
+
674
+ >>> imp = signal.unit_impulse(100, 'mid')
675
+ >>> b, a = signal.butter(4, 0.2)
676
+ >>> response = signal.lfilter(b, a, imp)
677
+
678
+ >>> import numpy as np
679
+ >>> import matplotlib.pyplot as plt
680
+ >>> plt.plot(np.arange(-50, 50), imp)
681
+ >>> plt.plot(np.arange(-50, 50), response)
682
+ >>> plt.margins(0.1, 0.1)
683
+ >>> plt.xlabel('Time [samples]')
684
+ >>> plt.ylabel('Amplitude')
685
+ >>> plt.grid(True)
686
+ >>> plt.show()
687
+
688
+ """
689
+ out = zeros(shape, dtype)
690
+
691
+ shape = np.atleast_1d(shape)
692
+
693
+ if idx is None:
694
+ idx = (0,) * len(shape)
695
+ elif idx == 'mid':
696
+ idx = tuple(shape // 2)
697
+ elif not hasattr(idx, '__iter__'):
698
+ idx = (idx,) * len(shape)
699
+
700
+ out[idx] = 1
701
+ return out