# pyfar.dsp¶

Classes:

 InterpolateSpectrum(data, method, kind[, ...]) Interpolate an incomplete spectrum to a complete single sided spectrum.

Functions:

 average(signal[, mode, caxis, weights, keepdims]) Average multi-channel signals. convolve(signal1, signal2[, mode, method]) Convolve two signals. decibel(signal[, domain, log_prefix, ...]) Convert data of the selected signal domain into decibels (dB). deconvolve(system_output, system_input[, ...]) Calculate transfer functions by spectral deconvolution of two signals. energy(signal) Computes the channel wise energy in the time domain find_impulse_response_delay(impulse_response) Find the delay in sub-sample values of an impulse response. find_impulse_response_start(impulse_response) Find the start sample of an impulse response. fractional_time_shift(signal, shift[, unit, ...]) Apply fractional time shift to input data. group_delay(signal[, frequencies, method]) Returns the group delay of a signal in samples. Return a shape parameter beta to create kaiser window based on desired side lobe suppression in dB. linear_phase(signal, group_delay[, unit]) Set the phase to a linear phase with a specified group delay. minimum_phase(signal[, n_fft, truncate]) Calculate the minimum phase equivalent of a finite impulse response. normalize(signal[, reference_method, ...]) Apply a normalization. pad_zeros(signal, pad_width[, mode]) Pad a signal with zeros in the time domain. phase(signal[, deg, unwrap]) Returns the phase for a given signal object. power(signal) Compute the power of a signal. regularized_spectrum_inversion(signal, ...) Invert the spectrum of a signal applying frequency dependent regularization. resample(signal, sampling_rate[, ...]) Resample signal to new sampling rate. rms(signal) Compute the root mean square (RMS) of a signal. smooth_fractional_octave(signal, num_fractions) Smooth spectrum with a fractional octave width. spectrogram(signal[, window, window_length, ...]) Compute the magnitude spectrum versus time. time_shift(signal, shift[, mode, unit, ...]) Apply a cyclic or linear time-shift to a signal. time_window(signal, interval[, window, ...]) Apply time window to signal. Wraps phase to 2 pi. zero_phase(signal) Calculate zero phase signal.
class pyfar.dsp.InterpolateSpectrum(data, method, kind, fscale='linear', clip=False, group_delay=None, unit='samples')[source]

Bases: object

Interpolate an incomplete spectrum to a complete single sided spectrum.

This is intended to interpolate transfer functions, for example sparse spectra that are defined only at octave frequencies or incomplete spectra from numerical simulations.

Parameters:
• data (FrequencyData) – Input data to be interpolated. data.fft_norm must be ‘none’.

• method (string) –

Specifies the input data for the interpolation

'complex'

Separate interpolation of the real and imaginary part

'magnitude_phase'

Separate interpolation if the magnitude and unwrapped phase values

'magnitude'

Interpolate the magnitude values only. Results in a zero phase signal, which is symmetric around the first sample. This phase response might not be ideal for many applications. Minimum and linear phase responses can be generated with minimum_phase and linear_phase.

• kind (tuple) –

Three element tuple ('first', 'second', 'third') that specifies the kind of inter/extrapolation below the lowest frequency (first), between the lowest and highest frequency (second), and above the highest frequency (third).

The individual strings have to be

'zero', slinear, 'quadratic', 'cubic'

Spline interpolation of zeroth, first, second or third order

'previous', 'next'

Simply return the previous or next value of the point

'nearest-up', 'nearest'

Differ when interpolating half-integers (e.g. 0.5, 1.5) in that 'nearest-up' rounds up and 'nearest' rounds down.

The interpolation is done using scipy.interpolate.interp1d.

• fscale (string, optional) –

'linear'

Interpolate on a linear frequency axis.

'log'

Interpolate on a logarithmic frequency axis. Note that 0 Hz can not be interpolated on a logarithmic scale because the logarithm of 0 does not exist. Frequencies of 0 Hz are thus replaced by the next highest frequency before interpolation.

The default is 'linear'.

• clip (bool, tuple) – The interpolated magnitude response is clipped to the range specified by this two element tuple. E.g., clip=(0, 1) will assure that no values smaller than 0 and larger than 1 occur in the interpolated magnitude response. The clipping is applied after the interpolation. The default is False which does not clip the data.

Returns:

interpolator – The interpolator can be called to interpolate the data (see examples below). It returns a Signal and has the following parameters

n_samplesint

Length of the interpolated time signal in samples

sampling_rate: int

Sampling rate of the output signal in Hz

showbool, optional

Show a plot of the input and output data. The default is False.

Return type:

InterpolateSpectrum

Examples

Interpolate a magnitude spectrum, add an artificial linear phase and inspect the results. Note that a similar plot can be created by the interpolator object by signal = interpolator(64, 44100, show=True)

>>> import pyfar as pf
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> # generate data
>>> data = pf.FrequencyData([1, 0], [5e3, 20e3])
>>> interpolator = pf.dsp.InterpolateSpectrum(
...     data, 'magnitude', ('nearest', 'linear', 'nearest'))
>>> # interpolate 64 samples at a sampling rate of 44100
>>> signal = interpolator(64, 44100)
>>> signal = pf.dsp.linear_phase(signal, 32)
>>> # plot input and output data
>>> with pf.plot.context():
>>>     _, ax = plt.subplots(2, 2)
>>>     # time signal (linear and logarithmic amplitude)
>>>     pf.plot.time(signal, ax=ax[0, 0])
>>>     pf.plot.time(signal, ax=ax[1, 0], dB=True)
>>>     # frequency plot (linear x-axis)
>>>     pf.plot.freq(signal, dB=False, freq_scale="linear",
...                  ax=ax[0, 1])
>>>     pf.plot.freq(data, dB=False, freq_scale="linear",
...                  ax=ax[0, 1], c='r', ls='', marker='.')
>>>     ax[0, 1].set_xlim(0, signal.sampling_rate/2)
>>>     # frequency plot (log x-axis)
>>>     pf.plot.freq(signal, dB=False, ax=ax[1, 1], label='input')
>>>     pf.plot.freq(data, dB=False, ax=ax[1, 1],
...                  c='r', ls='', marker='.', label='output')
>>>     min_freq = np.min([signal.sampling_rate / signal.n_samples,
...                        data.frequencies])
>>>     ax[1, 1].set_xlim(min_freq, signal.sampling_rate/2)
>>>     ax[1, 1].legend(loc='best')

pyfar.dsp.average(signal, mode='linear', caxis=None, weights=None, keepdims=False)[source]

Average multi-channel signals.

Parameters:
• signal (Signal, TimeData, FrequencyData) – Input signal.

• mode (string) –

'linear'

Average signal.time if the signal is in the time domain and signal.freq if the signal is in the frequency domain. Note that these operations are equivalent for Signal objects due to the linearity of the averaging and the FFT.

'magnitude_zerophase'

Average the magnitude spectra and discard the phase.

'magnitude_phase'

Average the magnitude spectra and the unwrapped phase separatly.

'power'

Average the power spectra $$|X|^2$$ and discard the phase. The squaring of the spectra is reversed before returning the averaged signal.

'log_magnitude_zerophase'

Average the logarithmic magnitude spectra using decibel and discard the phase. The logarithm is reversed before returning the averaged signal.

The default is 'linear'

• caxis (None, int, or tuple of ints, optional) – Channel axes along which the averaging is done. The default None averages across all channels. See audio classes for more information.

• weights (array like) – Array with channel weights for averaging the data. Must be broadcastable to signal.cshape. The default is None, which applies equal weights to all channels.

• keepdims (bool, optional) – If this is True, the axes which are reduced during the averaging are kept as a dimension with size one. Otherwise, singular dimensions will be squeezed after averaging. The default is False.

Returns:

averaged_signal – Averaged input Signal.

Return type:

Notes

The functions linear_phase and minimum_phase can be used to obtain a phase for magnitude spectra after using a mode that discards the phase.

Convolve two signals.

Parameters:
• signal1 (Signal) – The first signal

• signal2 (Signal) – The second signal

• mode (string, optional) –

A string indicating the size of the output:

'full'

Compute the full discrete linear convolution of the input signals. The output has the length 'signal1.n_samples + signal2.n_samples - 1' (Default).

'cut'

Compute the complete convolution with full and truncate the result to the length of the longer signal.

'cyclic'

The output is the cyclic convolution of the signals, where the shorter signal is zero-padded to fit the length of the longer one. This is done by computing the complete convolution with 'full', adding the tail (i.e., the part that is truncated for mode='cut' to the beginning of the result) and truncating the result to the length of the longer signal.

• method (str {'overlap_add', 'fft'}, optional) –

A string indicating which method to use to calculate the convolution:

'overlap_add'

Convolve using the overlap-add algorithm based on scipy.signal.oaconvolve. (Default)

'fft'

Convolve using FFT based on scipy.signal.fftconvolve.

See Notes for more details.

Returns:

The convolution result as a Signal object.

Return type:

Signal

Notes

The overlap-add method is generally much faster than fft convolution when one signal is much larger than the other, but can be slower when only a few output values are needed or when the signals have a very similar length. For method='overlap_add', integer data will be cast to float.

Examples

Illustrate the different modes.

>>> import pyfar as pf
>>> s1 = pf.Signal([1, 0.5, 0.5], 1000)
>>> s2 = pf.Signal([1,-1], 1000)
>>> full = pf.dsp.convolve(s1, s2, mode='full')
>>> cut = pf.dsp.convolve(s1, s2, mode='cut')
>>> cyc = pf.dsp.convolve(s1, s2, mode='cyclic')
>>> # Plot input and output
>>> with pf.plot.context():
>>>     fig, ax = plt.subplots(2, 1, sharex=True)
>>>     pf.plot.time(s1, ax=ax, label='Signal 1', marker='o')
>>>     pf.plot.time(s2, ax=ax, label='Signal 2', marker='o')
>>>     ax.set_title('Input Signals')
>>>     ax.legend()
>>>     pf.plot.time(full, ax=ax, label='full', marker='o')
>>>     pf.plot.time(cut, ax=ax, label='cut', ls='--',  marker='o')
>>>     pf.plot.time(cyc, ax=ax, label='cyclic', ls=':', marker='o')
>>>     ax.set_title('Convolution Result')
>>>     ax.set_ylim(-1.1, 1.1)
>>>     ax.legend()
>>>     fig.tight_layout()

pyfar.dsp.decibel(signal, domain='freq', log_prefix=None, log_reference=1, return_prefix=False)[source]

Convert data of the selected signal domain into decibels (dB).

The converted data is calculated by the base 10 logarithmic scale: data_in_dB = log_prefix * numpy.log10(data/log_reference). By using a logarithmic scale, the deciBel is able to compare quantities that may have vast ratios between them. As an example, the sound pressure in dB can be calculated as followed:

$L_p = 20\log_{10}\biggl(\frac{p}{p_0}\biggr),$

where $$20$$ is the logarithmic prefix for sound field quantities and $$p_0$$ would be the reference for the sound pressure level. A list of commonly used reference values can be found in the ‘log_reference’ parameters section.

Parameters:
• signal (Signal, TimeData, FrequencyData) – The signal which is converted into decibel

• domain (str) –

The domain, that is converted to decibels:

'freq'

Convert normalized frequency domain data. Signal must be of type ‘Signal’ or ‘FrequencyData’.

'time'

Convert time domain data. Signal must be of type ‘Signal’ or ‘TimeData’.

'freq_raw'

Convert frequency domain data without normalization. Signal must be of type ‘Signal’.

The default is 'freq'.

• log_prefix (int) – The prefix for the dB calculation. The default None, uses 10 for signals with 'psd' and 'power' FFT normalization and 20 otherwise.

• log_reference (int or float) –

Reference for the logarithm calculation. List of commonly used values:

log_reference

value

Digital signals (dBFs)

1

Sound pressure $$L_p$$ (dB)

2e-5 Pa

Voltage $$L_V$$ (dBu)

0.7746 volt

Sound intensity $$L_I$$ (dB)

1e-12 W/m²

Voltage $$L_V$$ (dBV)

1 volt

Electric power $$L_P$$ (dB)

1 watt

The default is 1.

• return_prefix (bool, optional) – If return_prefix is True, the function will also return the log_prefix value. This can be used to delogrithmize the data. The default is False.

Returns:

• decibel (numpy.ndarray) – The given signal in decibel in chosen domain.

• log_prefix (int or float) – Will be returned if return_prefix is set to True.

Examples

>>> import pyfar as pf
>>> signal = pf.signals.noise(41000, rms=[1, 1])
>>> decibel_data = decibel(signal, domain='time')

pyfar.dsp.deconvolve(system_output, system_input, fft_length=None, freq_range=None, **kwargs)[source]

Calculate transfer functions by spectral deconvolution of two signals.

The transfer function $$H(\omega)$$ is calculated by spectral deconvolution (spectral division).

$H(\omega) = \frac{Y(\omega)}{X(\omega)},$

where $$X(\omega)$$ is the system input signal and $$Y(\omega)$$ the system output. Regularized inversion is used to avoid numerical issues in calculating $$X(\omega)^{-1} = 1/X(\omega)$$ for small values of $$X(\omega)$$ (see regularized_spectrum_inversion). The system response (transfer function) is thus calculated as

$H(\omega) = Y(\omega)X(\omega)^{-1}.$

Parameters:
• system_output (Signal) – The system output signal (e.g., recorded after passing a device under test). The system output signal is zero padded, if it is shorter than the system input signal.

• system_input (Signal) – The system input signal (e.g., used to perform a measurement). The system input signal is zero padded, if it is shorter than the system output signal.

• freq_range (tuple, array_like, double) – The upper and lower frequency limits outside of which the regularization factor is to be applied. The default None bypasses the regularization, which might cause numerical instabilities in case of band-limited system_input. Also see regularized_spectrum_inversion.

• fft_length (int or None) – The length the signals system_output and system_input are zero padded to before deconvolving. The default is None. In this case only the shorter signal is padded to the length of the longer signal, no padding is applied when both signals have the same length.

• kwargs (key value arguments) – Key value arguments to control the inversion of $$H(\omega)$$ are passed to to regularized_spectrum_inversion.

Returns:

system_response – The resulting signal after deconvolution, representing the system response (the transfer function). The fft_norm of is set to 'none'.

Return type:

Signal

References

pyfar.dsp.energy(signal)[source]

Computes the channel wise energy in the time domain

$\sum_{n=0}^{N-1}|x[n]|^2=\frac{1}{N}\sum_{k=0}^{N-1}|X[k]|^2,$

which is equivalent to the frequency domain computation according to Parseval’s theorem .

Parameters:

signal (Signal) – The signal to compute the energy from.

Returns:

data – The channel-wise energy of the input signal.

Return type:

numpy.ndarray

Notes

Due to the calculation based on the time data, the returned energy is independent of the signal’s fft_norm. power and rms can be used to compute the power and the rms of a signal.

References

pyfar.dsp.find_impulse_response_delay(impulse_response, N=1)[source]

Find the delay in sub-sample values of an impulse response.

The method relies on the analytic part of the cross-correlation function of the impulse response and it’s minimum-phase equivalent, which is zero for the maximum of the correlation function. For sub-sample root finding, the analytic signal is approximated using a polynomial of order N. The algorithm is based on  with the following modifications:

1. Values with negative gradient used for polynolmial fitting are rejected, allowing to use larger part of the signal for fitting.

2. By default a first order polynomial is used, as the slope of the analytic signal should in theory be linear.

Alternatively see pyfar.dsp.find_impulse_response_start.

Parameters:
• impulse_response (Signal) – The impulse response.

• N (int, optional) – The order of the polynom used for root finding, by default 1.

Returns:

delay – Delay of the impulse response, as an array of shape signal.cshape. Can be floating point values in the case of sub-sample values.

Return type:

numpy.ndarray, float

References

Examples

Create a band-limited impulse shifted by 0.5 samples and estimate the starting sample of the impulse and plot.

>>> import pyfar as pf
>>> import numpy as np
>>> n_samples = 64
>>> delay_samples = n_samples // 2 + 1/2
>>> ir = pf.signals.impulse(n_samples)
>>> ir = pf.dsp.linear_phase(ir, delay_samples, unit='samples')
>>> start_samples = pf.dsp.find_impulse_response_delay(ir)
>>> ax = pf.plot.time(ir, unit='ms', label='impulse response')
>>> ax.axvline(
...     start_samples/ir.sampling_rate*1e3,
...     color='k', linestyle='-.', label='start sample')
>>> ax.legend()

pyfar.dsp.find_impulse_response_start(impulse_response, threshold=20)[source]

Find the start sample of an impulse response.

The start sample is identified as the first sample which is below the threshold level relative to the maximum level of the impulse response. For room impulse responses, ISO 3382  specifies a threshold of 20 dB. This function is primary intended to be used when processing room impulse responses. Alternatively see pyfar.dsp.find_impulse_response_delay.

Parameters:
• impulse_response (pyfar.Signal) – The impulse response

• threshold (float, optional) – The threshold level in dB, by default 20, which complies with ISO 3382.

Returns:

start_sample – Sample at which the impulse response starts

Return type:

numpy.ndarray, int

Notes

The function tries to estimate the PSNR in the IR based on the signal power in the last 10 percent of the IR. The automatic estimation may fail if the noise spectrum is not white or the impulse response contains non-linear distortions. If the PSNR is lower than the specified threshold, the function will issue a warning.

References

Examples

Create a band-limited impulse shifted by 0.5 samples and estimate the starting sample of the impulse and plot.

>>> import pyfar as pf
>>> import numpy as np
>>> n_samples = 256
>>> delay_samples = n_samples // 2 + 1/2
>>> ir = pf.signals.impulse(n_samples)
>>> ir = pf.dsp.linear_phase(ir, delay_samples, unit='samples')
>>> start_samples = pf.dsp.find_impulse_response_start(ir)
>>> ax = pf.plot.time(ir, unit='ms', label='impulse response', dB=True)
>>> ax.axvline(
...     start_samples/ir.sampling_rate*1e3,
...     color='k', linestyle='-.', label='start sample')
>>> ax.axhline(
...     20*np.log10(np.max(np.abs(ir.time)))-20,
...     color='k', linestyle=':', label='threshold')
>>> ax.legend()


Create a train of weighted impulses with levels below and above the threshold, serving as a very abstract room impulse response. The starting sample is identified as the last sample below the threshold relative to the maximum of the impulse response.

>>> import pyfar as pf
>>> import numpy as np
>>> n_samples = 64
>>> delays = np.array([14, 22, 26, 30, 33])
>>> amplitudes = np.array([-35, -22, -6, 0, -9], dtype=float)
>>> ir = pf.signals.impulse(n_samples, delays, 10**(amplitudes/20))
>>> ir.time = np.sum(ir.time, axis=0)
>>> start_sample_est = pf.dsp.find_impulse_response_start(
...     ir, threshold=20)
>>> ax = pf.plot.time(
...     ir, dB=True, unit='samples',
...     label=f'peak samples: {delays}')
>>> ax.axvline(
...     start_sample_est, linestyle='-.', color='k',
...     label=f'ir start sample: {start_sample_est}')
>>> ax.axhline(
...     20*np.log10(np.max(np.abs(ir.time)))-20,
...     color='k', linestyle=':', label='threshold')
>>> ax.legend()

pyfar.dsp.fractional_time_shift(signal, shift, unit='samples', order=30, side_lobe_suppression=60, mode='linear')[source]

Apply fractional time shift to input data.

This function uses a windowed Sinc filter (Method FIR-2 in  according to Equations 21 and 22) to apply fractional delays, i.e., non-integer delays to an input signal. A Kaiser window according to  Equations (10.12) and (10.13) is used, which offers the possibility to control the side lobe suppression.

Parameters:
• signal (Signal) – The input data

• shift (float, array like) – The fractional shift in samples (positive or negative). If this is a float, the same shift is applied to all channels of signal. If this is an array like different delays are applied to the channels of signal. In this case it must broadcast to signal.cshape (see Numpy broadcasting)

• order (int, optional) – The order of the fractional shift (sinc) filter. The precision of the filter increases with the order. High frequency errors decrease with increasing order. The order must be smaller than signal.n_samples. The default is 30.

• side_lobe_suppression (float, optional) – The side lobe suppression of the Kaiser window in dB. The default is 60.

• mode (str, optional) –

The filtering mode

"linear"

Apply linear shift, i.e., parts of the signal that are shifted to times smaller than 0 samples and larger than signal.n_samples disappear.

"cyclic"

Apply a cyclic shift, i.e., parts of the signal that are shifted to values smaller than 0 are wrapped around to the end, and parts that are shifted to values larger than signal.n_samples are wrapped around to the beginning.

The default is "linear"

Returns:

signal – The delayed input data

Return type:

Signal

References

Examples

Apply a fractional shift of 2.3 samples using filters of orders 6 and 30

>>> import pyfar as pf
>>> import matplotlib.pyplot as plt
>>>
>>> signal = pf.signals.impulse(64, 10)
>>>
>>> pf.plot.use()
>>> _, ax = plt.subplots(3, 1, figsize=(8, 8))
>>> pf.plot.time_freq(signal, ax=ax[:2], label="input")
>>> pf.plot.group_delay(signal, ax=ax, unit="samples")
>>>
>>> for order in [30, 6]:
>>>     delayed = pf.dsp.fractional_time_shift(
...         signal, 2.3, order=order)
>>>     pf.plot.time_freq(delayed, ax=ax[:2],
...                       label=f"delayed, order={order}")
>>>     pf.plot.group_delay(delayed, ax=ax, unit="samples")
>>>
>>> ax.set_ylim(-15, 5)
>>> ax.set_ylim(8, 14)
>>> ax.legend()


Apply a shift that exceeds the signal length using the modes "linear" and "cyclic"

>>> import pyfar as pf
>>>
>>> signal = pf.signals.impulse(32, 16)
>>>
>>> ax = pf.plot.time(signal, label="input")
>>>
>>> for mode in ["cyclic", "linear"]:
>>>     delayed = pf.dsp.fractional_time_shift(
...         signal, 25.3, order=10, mode=mode)
>>>     pf.plot.time(delayed, label=f"delayed, mode={mode}")
>>>
>>> ax.legend()

pyfar.dsp.group_delay(signal, frequencies=None, method='fft')[source]

Returns the group delay of a signal in samples.

Parameters:
• signal (Signal) – An audio signal object from the pyfar signal class

• frequencies (array-like) – Frequency or frequencies in Hz at which the group delay is calculated. The default is None, in which case signal.frequencies is used.

• method ('scipy', 'fft', optional) – Method to calculate the group delay of a Signal. Both methods calculate the group delay using the method presented in  avoiding issues due to discontinuities in the unwrapped phase. Note that the scipy version additionally allows to specify frequencies for which the group delay is evaluated. The default is 'fft', which is faster.

Returns:

group_delay – Frequency dependent group delay in samples. The array is flattened if a single channel signal was passed to the function.

Return type:

numpy array

References

pyfar.dsp.kaiser_window_beta(A)[source]

Return a shape parameter beta to create kaiser window based on desired side lobe suppression in dB.

This function can be used to call time_window with window=('kaiser', beta).

Parameters:

A (float) – Side lobe suppression in dB

Returns:

beta – Shape parameter beta after , Eq. 7.75

Return type:

float

References

pyfar.dsp.linear_phase(signal, group_delay, unit='samples')[source]

Set the phase to a linear phase with a specified group delay.

The linear phase signal is computed as

$H_{\mathrm{lin}} = |H| \mathrm{e}^{-j \omega \tau}\,,$

with $$H$$ the complex spectrum of the input data, $$|\cdot|$$ the absolute values, $$\omega$$ the frequency in radians and $$\tau$$ the group delay in seconds.

Parameters:
• signal (Signal) – input data

• group_delay (float, array like) – The desired group delay of the linear phase signal according to unit. A reasonable value for most cases is signal.n_samples / 2 samples, which results in a time signal that is symmetric around the center. If group delay is a list or array it must broadcast with the channel layout of the signal (signal.cshape).

• unit (string, optional) – Unit of the group delay. Can be 'samples' or 's' for seconds. The default is 'samples'.

Returns:

signal – linear phase copy of the input data

Return type:

Signal

pyfar.dsp.minimum_phase(signal, n_fft=None, truncate=True)[source]

Calculate the minimum phase equivalent of a finite impulse response.

The method is based on the Hilbert transform of the real-valued cepstrum of the finite impulse response, that is the cepstrum of the magnitude spectrum only. As a result the magnitude spectrum is not distorted. Potential aliasing errors can occur due to the Fourier transform based calculation of the magnitude spectrum, which however are negligible if the length of Fourier transform n_fft is sufficiently high.  (Section 8.5.4)

Parameters:
• signal (Signal) – The finite impulse response for which the minimum-phase version is computed.

• n_fft (int, optional) – The FFT length used for calculating the cepstrum. Should be at least a few times larger than signal.n_samples. The default None uses eight times the signal length rounded up to the next power of two, that is: 2**int(np.ceil(np.log2(n_samples * 8))).

• truncate (bool, optional) – If truncate is True, the resulting minimum phase impulse response is truncated to a length of signal.n_samples//2 + signal.n_samples % 2. This avoids aliasing described above in any case but might distort the magnitude response if signal.n_samples is to low. If truncate is False the output signal has the same length as the input signal. The default is True.

Returns:

signal_minphase – The minimum phase version of the input data.

Return type:

Signal

References

Examples

Create a minimum phase equivalent of a linear phase FIR low-pass filter

>>> import pyfar as pf
>>> import numpy as np
>>> from scipy.signal import remez
>>> import matplotlib.pyplot as plt
>>> freq = [0, 0.2, 0.3, 1.0]
>>> h_linear = pf.Signal(remez(151, freq, [1, 0], Hz=2.), 44100)
>>> # create minimum phase impulse responses
>>> h_min = pf.dsp.minimum_phase(h_linear, truncate=False)
>>> # plot the result
>>> fig, axs = plt.subplots(3, figsize=(8, 6))
>>> pf.plot.time(h_linear, ax=axs)
>>> pf.plot.time(h_min, ax=axs)
>>> axs.grid(True)
>>> pf.plot.freq(h_linear, ax=axs)
>>> pf.plot.group_delay(h_linear, ax=axs, unit="ms")
>>> pf.plot.freq(h_min, ax=axs)
>>> pf.plot.group_delay(h_min, ax=axs, unit="ms")
>>> axs.legend(['Linear', 'Minimum'], loc=3, ncol=2)
>>> axs.set_ylim(-2.5, 2.5)

pyfar.dsp.normalize(signal, reference_method='max', domain='time', channel_handling='individual', target=1, limits=(None, None), unit=None, return_reference=False)[source]

Apply a normalization.

In the default case, the normalization ensures that the maximum absolute amplitude of the signal after normalization is 1. This is achieved by the multiplication

signal_normalized = signal * target / reference,

where target equals 1 and reference is the maximum absolute amplitude before the normalization.

Several normalizations are possible, which in fact are different ways of computing the reference value (e.g. based on the spectrum). See the parameters for details.

Parameters:
• signal (Signal, TimeData, FrequencyData) – Input signal.

• domain (string) –

Determines which data is used to compute the reference value.

'time'

Use the absolute of the time domain data np.abs(signal.time).

'freq'

Use the magnitude spectrum np.abs(signal.freq). Note that the normalized magnitude spectrum used (cf.:py:mod:FFT concepts <pyfar._concepts.fft>).

The default is 'time'.

• reference_method (string, optional) –

Reference method to compute the channel-wise reference value using the data according to domain.

'max'

Compute the maximum absolute value per channel.

'mean'

Compute the mean absolute values per channel.

'energy'

Compute the energy per channel using energy.

'power'

Compute the power per channel using power.

'rms'

Compute the RMS per channel using rms.

The default is 'max'.

• channel_handling (string, optional) –

Define how channel-wise reference values are handeled for multi-

channel signals. This parameter does not affect single-channel signals.

'individual'

Separate normalization of each channel individually.

'max'

Normalize to the maximum reference value across channels.

'min'

Normalize to the minimum reference value across channels.

'mean'

Normalize to the mean reference value across the channels.

The default is 'individual'.

• target (scalar, array) – The target to which the signal is normalized. Can be a scalar or an array. In the latter case the shape of target must be broadcastable to signal.cshape. The default is 1.

• limits (tuple, array_like) – Restrict the time or frequency range that is used to compute the reference value. Two element tuple specifying upper and lower limit according to domain and unit. A None element means no upper or lower limitation. The default (None, None) uses the entire signal. Note that in case of limiting in samples or bins with unit=None, the second value defines the first sample/bin that is excluded. Also note that limits need to be (None, None) if reference_method is rms, power or energy.

• unit (string, optional) –

Unit of limits.

's'

Set limits in seconds in case of time domain normalization. Uses find_nearest_time to find the limits.

'Hz'

Set limits in hertz in case of frequency domain normalization. Uses find_nearest_frequency

The default None assumes that limits is given in samples in case of time domain normalization and in bins in case of frequency domain normalization.

• return_reference (bool) – If return_reference=True, the function also returns the reference values for the channels. The default is False.

Returns:

• normalized_signal (Signal, TimeData, FrequencyData) – The normalized input signal.

• reference_norm (numpy.ndarray) – The reference values used for normalization. Only returned if return_reference is True.

Examples

Time domain normalization with default parameters

>>> import pyfar as pf
>>> signal = pf.signals.sine(1e3, 441, amplitude=2)
>>> signal_norm = pf.dsp.normalize(signal)
>>> # Plot input and normalized Signal
>>> ax = pf.plot.time(signal, label='Original Signal')
>>> pf.plot.time(signal_norm, label='Normalized Signal')
>>> ax.legend()


Frequency normalization with a restricted frequency range and targed in dB

>>> import pyfar as pf
>>> sine1 = pf.signals.sine(1e3, 441, amplitude=2)
>>> sine2 = pf.signals.sine(5e2, 441, amplitude=.5)
>>> signal = sine1 + sine2
>>> # Normalize to dB target in restricted frequency range
>>> target_dB = 0
>>> signal_norm = pf.dsp.normalize(signal, target=10**(target_dB/20),
...     domain="freq", limits=(400, 600), unit="Hz")
>>> # Plot input and normalized Signal
>>> ax = pf.plot.time_freq(signal_norm, label='Normalized Signal')
>>> pf.plot.time_freq(signal, label='Original Signal')
>>> ax.set_ylim(-15, 15)
>>> ax.legend()


Pad a signal with zeros in the time domain.

Parameters:
• signal (Signal) – The signal which is to be extended.

• mode (str, optional) –

'after'

Append zeros to the end of the signal

'before'

Pre-pend zeros before the starting time of the signal

'center'

Insert the number of zeros in the middle of the signal. This mode can be used to pad signals with a symmetry with respect to the time t=0.

The default is 'after'.

Returns:

Return type:

Signal

Examples

>>> import pyfar as pf
>>> impulse = pf.signals.impulse(512, amplitude=1)

pyfar.dsp.phase(signal, deg=False, unwrap=False)[source]

Returns the phase for a given signal object.

Parameters:
• signal (Signal, FrequencyData) – pyfar Signal or FrequencyData object.

• deg (Boolean) – Specifies, whether the phase is returned in degrees or radians.

• unwrap (Boolean) – Specifies, whether the phase is unwrapped or not. If set to '360', the phase is wrapped to 2 pi.

Returns:

phase – The phase of the signal.

Return type:

numpy array

pyfar.dsp.power(signal)[source]

Compute the power of a signal.

The power is calculated as

$\frac{1}{N}\sum_{n=0}^{N-1}|x[n]|^2$

based on the time data for each channel separately.

Parameters:

signal (Signal) – The signal to compute the power from.

Returns:

data – The channel-wise power of the input signal.

Return type:

numpy.ndarray

Notes

Due to the calculation based on the time data, the returned power is independent of the signal’s fft_norm. The power equals the squared RMS of a signal. energy and rms can be used to compute the energy and the RMS.

pyfar.dsp.regularized_spectrum_inversion(signal, freq_range, regu_outside=1.0, regu_inside=1e-10, regu_final=None, normalized=True)[source]

Invert the spectrum of a signal applying frequency dependent regularization.

Regularization can either be specified within a given frequency range using two different regularization factors, or for each frequency individually using the parameter regu_final. In the first case the regularization factors for the frequency regions are cross-faded using a raised cosine window function with a width of $$\sqrt{2}f$$ above and below the given frequency range. Note that the resulting regularization function is adjusted to the quadratic maximum of the given signal. In case the regu_final parameter is used, all remaining options are ignored and an array matching the number of frequency bins of the signal needs to be given. In this case, no normalization of the regularization function is applied.

Finally, the inverse spectrum is calculated as , ,

$S^{-1}(f) = \frac{S^*(f)}{S^*(f)S(f) + \epsilon(f)}$
Parameters:
• signal (Signal) – The signals which spectra are to be inverted.

• freq_range (tuple, array_like, double) – The upper and lower frequency limits outside of which the regularization factor is to be applied.

• regu_outside (float, optional) – The normalized regularization factor outside the frequency range. The default is 1.

• regu_inside (float, optional) – The normalized regularization factor inside the frequency range. The default is 10**(-200/20) (-200 dB).

• regu_final (float, array_like, optional) – The final regularization factor for each frequency, default None. If this parameter is set, the remaining regularization factors are ignored.

• normalized (bool) – Flag to indicate if the normalized spectrum (according to signal.fft_norm) should be inverted. The default is True.

Returns:

The resulting signal after inversion.

Return type:

Signal

References

pyfar.dsp.resample(signal, sampling_rate, match_amplitude='auto', frac_limit=None, post_filter=False)[source]

Resample signal to new sampling rate.

The SciPy function scipy.signal.resample_poly is used for resampling. The resampling ratio L = sampling_rate/signal.sampling_rate is approximated by a fraction of two integer numbers up/down to first upsample the signal by up and then downsample by down. This way up and down are smaller than the respective new and old sampling rates.

Note

sampling_rate should be divisible by 10, otherwise it can cause an infinite loop in the resample_poly function.

The amplitudes of the resampled signal can match the amplitude of the input signal in the time or frequency domain. See the parameter match_amplitude and the examples for more information.

Parameters:
• signal (Signal) – Input data to be resampled

• sampling_rate (number) – The new sampling rate in Hz

• match_amplitude (string) –

Define the domain to match the amplitude of the resampled data.

'auto'

Chooses domain to maintain the amplitude automatically, depending on the signal.signal_type. Sets match_amplitude == 'freq' for signal.signal_type = 'energy' like impulse responses and match_amplitude == 'time' for other signals.

'time'

Maintains the amplitude in the time domain. This is useful for recordings such as speech or music and must be used if signal.signal_type = 'power'.

'freq'

Maintains the amplitude in the frequency domain by multiplying the resampled signal by 1/L (see above). This is often desired when resampling impulse responses.

The default is 'auto'.

• frac_limit (int) –

Limit the denominator for approximating the resampling factor L (see above). This can be used in case the resampling gets stuck in an infinite loop (see note above) at the potenital cost of not exactly realizing the target sampling rate.

The default is None, which uses frac_limit = 1e6.

• post_filter (bool, optional) – In some cases the up-sampling causes artifacts above the Nyquist frequency of the input signal, i.e., signal.sampling_rate/2. If True the artifacts are suppressed by applying a zero-phase Elliptic filter with a pass band ripple of 0.1 dB, a stop band attenuation of 60 dB. The pass band edge frequency is signal.sampling_rate/2. The stop band edge frequency is the minimum of 1.05 times the pass band frequency and the new Nyquist frequency (sampling_rate/2). The default is False. Note that this is only applied in case of up-sampling.

Returns:

signal – The resampled signal of the input data with a length of up/down * signal.n_samples samples.

Return type:

pyfar.Signal

Examples

For power signals, the amplitude of the resampled signal is automatically correct in the time and frequency domain if match_amplitude="time"

>>> import pyfar as pf
>>> import matplotlib.pyplot as plt
>>>
>>> signal = pf.signals.sine(200, 4800, sampling_rate=48000)
>>> resampled = pf.dsp.resample(signal, 96000)
>>>
>>> pf.plot.time_freq(signal, label="original")
>>> pf.plot.time_freq(resampled, c="y", ls=":",
...                   label="resampled (time domain matched)")
>>> plt.legend()


With some energy signals, such as impulse responses, the amplitude can only be correct in the time or frequency domain due to the lack of normalization by the number of samples. In such cases, it is often desired to match the amplitude in the frequency domain

>>> import pyfar as pf
>>> import matplotlib.pyplot as plt
>>>
>>> signal = pf.signals.impulse(128, 64, sampling_rate=48000)
>>> resampled_time = pf.dsp.resample(
...     signal, 96000, match_amplitude = "time")
>>> resampled_freq = pf.dsp.resample(
...     signal, 96000, match_amplitude = "freq")
>>>
>>> pf.plot.time_freq(signal, label="original")
>>> pf.plot.time_freq(resampled_freq, dashes=[2, 3],
...                   label="resampled (freq. domain matched)")
>>> ax = pf.plot.time_freq(resampled_time, ls=":",
...                   label="resampled (time domain matched)", c='y')
>>> ax.set_xlim(1.2,1.46)
>>> plt.legend()

pyfar.dsp.rms(signal)[source]

Compute the root mean square (RMS) of a signal.

The RMS is calculated as

$\sqrt{\frac{1}{N}\sum_{n=0}^{N-1}|x[n]|^2}$

based on the time data for each channel separately.

Parameters:

signal (Signal) – The signal to compute the RMS from.

Returns:

data – The channel-wise RMS of the input signal.

Return type:

numpy.ndarray

Notes

The RMS equals the square root of the signal’s power. energy and power can be used to compute the energy and the power.

pyfar.dsp.smooth_fractional_octave(signal, num_fractions, mode='magnitude_zerophase', window='boxcar')[source]

Smooth spectrum with a fractional octave width.

The smoothing is done according to Tylka et al. 2017  (method 2) in three steps:

1. Interpolate the spectrum to a logarithmically spaced frequency scale

2. Smooth the spectrum by convolution with a smoothing window

3. Interpolate the spectrum to the original linear frequency scale

Parameters:
• signal (pyfar.Signal) – The input data.

• num_fractions (number) – The width of the smoothing window in fractional octaves, e.g., 3 will apply third octave smoothing and 1 will apply octave smoothing.

• mode (str, optional) –

"magnitude_zerophase"

Only the magnitude response, i.e., the absolute spectrum is smoothed. Note that this return a zero-phase signal. It might be necessary to generate a minimum or linear phase if the data is subject to further processing after the smoothing (cf. minimum_phase and linear_phase)

"magnitude"

Smooth the magnitude and keep the phase of the input signal.

"magnitude_phase"

Separately smooth the magnitude and unwrapped phase response.

"complex"

Separately smooth the real and imaginary part of the spectrum.

Note that the modes magnitude_zerophase and magnitude make sure that the smoothed magnitude response is as expected at the cost of an artificial phase response. This is often desired, e.g., when plotting signals or designing compensation filters. The modes magnitude_phase and complex smooth all information but might cause a high frequency energy loss in the smoothed magnitude response. The default is "magnitude_zerophase".

• window (str, optional) – String that defines the smoothing window. All windows from time_window that do not require an additional parameter can be used. The default is “boxcar”, which uses the most commonly used rectangular window.

Returns:

• signal (pyfar.Signal) – The smoothed output data

• window_stats (tuple) – A tuple containing information about the smoothing process

n_window

The window length in (logarithmically spaced) samples

num_fractions

The actual width of the window in fractional octaves. This can deviate from the desired width because the smoothing window must have an integer sample length

Notes

Method 3 in Tylka at al. 2017 is mathematically more elegant at the price of a largely increased computational and memory cost. In most practical cases, methods 2 and 3 yield close to identical results (cf. Fig. 2 and 3 in Tylka et al. 2017). If the spectrum contains extreme discontinuities, however, method 3 is superior (see examples below).

References

Examples

Octave smoothing of continuous spectrum consisting of two bell filters.

>>> import pyfar as pf
>>> signal = pf.signals.impulse(441)
>>> signal = pf.dsp.filter.bell(signal, 1e3, 12, 1, "III")
>>> signal = pf.dsp.filter.bell(signal, 10e3, -60, 100, "III")
>>> smoothed, _ = pf.dsp.smooth_fractional_octave(signal, 1)
>>> ax = pf.plot.freq(signal, label="input")
>>> pf.plot.freq(smoothed, label="smoothed")
>>> ax.legend(loc=3)


Octave smoothing of the discontinuous spectrum of a sine signal causes artifacts at the edges due to the intermediate interpolation steps (cf. Tylka et al. 2017, Fig. 4). However this is a rather unusual application and is mentioned only for the sake of completeness.

>>> import pyfar as pf
>>> signal = pf.signals.sine(1e3, 4410)
>>> signal.fft_norm = "amplitude"
>>> smoothed, _ = pf.dsp.smooth_fractional_octave(signal, 1)
>>> ax = pf.plot.freq(signal, label="input")
>>> pf.plot.freq(smoothed, label="smoothed")
>>> ax.set_xlim(200, 4e3)
>>> ax.set_ylim(-45, 5)
>>> ax.legend(loc=3)

pyfar.dsp.spectrogram(signal, window='hann', window_length=1024, window_overlap_fct=0.5, normalize=True)[source]

Compute the magnitude spectrum versus time.

This is a wrapper for scipy.signal.spectogram with two differences. First, the returned times refer to the start of the FFT blocks, i.e., the first time is always 0 whereas it is window_length/2 in scipy. Second, the returned spectrogram is normalized according to signal.fft_norm if the normalize parameter is set to True.

Parameters:
• signal (Signal) – Signal to compute spectrogram of.

• window (str) – Specifies the window (see scipy.signal.windows). The default is 'hann'.

• window_length (integer) – Window length in samples, the default ist 1024.

• window_overlap_fct (double) – Ratio of points to overlap between FFT segments [0…1]. The default is 0.5.

• normalize (bool) – Flag to indicate if the FFT normalization should be applied to the spectrogram according to signal.fft_norm. The default is True.

Returns:

• frequencies (numpy array) – Frequencies in Hz at which the magnitude spectrum was computed

• times (numpy array) – Times in seconds at which the magnitude spectrum was computed

• spectrogram (numpy array)

Apply a cyclic or linear time-shift to a signal.

This function only allows integer value sample shifts. If unit 'time' is used, the shift samples will be rounded to the nearest integer value. For a shift using fractional sample values see fractional_time_shift.

Parameters:
• signal (Signal) – The signal to be shifted

• shift (int, float) – The time-shift value. A positive value will result in right shift on the time axis (delaying of the signal), whereas a negative value yields a left shift on the time axis (non-causal shift to a earlier time). If a single value is given, the same time shift will be applied to each channel of the signal. Individual time shifts for each channel can be performed by passing an array matching the signals channel dimensions cshape.

• mode (str, optional) –

The shifting mode

"linear"

Apply linear shift, i.e., parts of the signal that are shifted to times smaller than 0 samples and larger than signal.n_samples disappear. To maintain the shape of the signal, the signal is padded at the respective other end. The pad value is determined by pad_type.

"cyclic"

Apply a cyclic shift, i.e., parts of the signal that are shifted to values smaller than 0 are wrapped around to the end, and parts that are shifted to values larger than signal.n_samples are wrapped around to the beginning.

The default is "cyclic"

• unit (str, optional) – Unit of the shift variable, this can be either 'samples' or 's' for seconds. By default 'samples' is used. Note that in the case of specifying the shift time in seconds, the value is rounded to the next integer sample value to perform the shift.

• pad_type (numeric, optional) – The pad value for linear shifts, by default 0. is used. Pad numpy.nan to the respective channels if the rms value of the signal is to be maintained for block-wise rms estimation of the noise power of a signal. Note that if NaNs are padded, the returned data will be a TimeData instead of Signal object.

Returns:

The time-shifted signal. This is a TimeData object in case a linear shift was done and the signal was padded with Nans. In all other cases, a Signal object is returend.

Return type:

Examples

Individually do a cyclic shift of a set of ideal impulses stored in three different channels and plot the resulting signals

>>> import pyfar as pf
>>> import matplotlib.pyplot as plt
>>> # generate and shift the impulses
>>> impulse = pf.signals.impulse(
...     32, amplitude=(1, 1.5, 1), delay=(14, 15, 16))
>>> shifted = pf.dsp.time_shift(impulse, [-2, 0, 2])
>>> # time domain plot
>>> pf.plot.use('light')
>>> _, axs = plt.subplots(2, 1)
>>> pf.plot.time(impulse, ax=axs)
>>> pf.plot.time(shifted, ax=axs)
>>> axs.set_title('Original signals')
>>> axs.set_title('Shifted signals')
>>> plt.tight_layout()


>>> import pyfar as pf
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> # generate and shift the impulses
>>> impulse = pf.signals.impulse(
...     32, amplitude=(1, 1.5, 1), delay=(14, 15, 16))
>>> shifted = pf.dsp.time_shift(
...     impulse, [-2, 0, 2], mode='linear', pad_value=np.nan)
>>> # time domain plot
>>> pf.plot.use('light')
>>> _, axs = plt.subplots(2, 1)
>>> pf.plot.time(impulse, ax=axs)
>>> pf.plot.time(shifted, ax=axs)
>>> axs.set_title('Original signals')
>>> axs.set_title('Shifted signals')
>>> plt.tight_layout()

pyfar.dsp.time_window(signal, interval, window='hann', shape='symmetric', unit='samples', crop='none', return_window=False)[source]

Apply time window to signal.

This function uses the windows implemented in scipy.signal.windows.

Parameters:
• signal (Signal) – Signal object to be windowed.

• interval (array_like) – If interval has two entries, these specify the beginning and the end of the symmetric window or the fade-in / fade-out (see parameter shape). If interval has four entries, a window with fade-in between the first two entries and a fade-out between the last two is created, while it is constant in between (ignores shape). The unit of interval is specified by the parameter unit. See below for more details.

• window (string, float, or tuple, optional) – The type of the window. See below for a list of implemented windows. The default is 'hann'.

• shape (string, optional) –

'symmetric'

General symmetric window, the two values in interval define the first and last samples of the window.

'symmetric_zero'

Symmetric window with respect to t=0, the two values in interval define the first and last samples of fade-out. crop is ignored.

'left'

Fade-in, the beginning and the end of the fade is defined by the two values in interval. See Notes for more details.

'right'

Fade-out, the beginning and the end of the fade is defined by the two values in interval. See Notes for more details.

The default is 'symmetric'.

• unit (string, optional) – Unit of interval. Can be set to 'samples' or 's' (seconds). Time values are rounded to the nearest sample. The default is 'samples'.

• crop (string, optional) –

'none'

The length of the windowed signal stays the same.

'window'

The signal is truncated to the windowed part.

'end'

Only the zeros at the end of the windowed signal are cropped, so the original phase is preserved.

The default is 'none'.

• return_window (bool, optional) – If True, both the windowed signal and the time window are returned. The default is False.

Returns:

• signal_windowed (Signal) – Windowed signal object

• window (Signal) – Time window used to create the windowed signal, only returned if return_window=True.

Notes

For a fade-in, the indexes of the samples given in interval denote the first sample of the window which is non-zero and the first which is one. For a fade-out, the samples given in interval denote the last sample which is one and the last which is non-zero.

This function calls scipy.signal.windows.get_window to create the window. Available window types:

• boxcar

• triang

• blackman

• hamming

• hann

• bartlett

• flattop

• parzen

• bohman

• blackmanharris

• nuttall

• barthann

• kaiser (needs beta, see kaiser_window_beta)

• gaussian (needs standard deviation)

• general_gaussian (needs power, width)

• dpss (needs normalized half-bandwidth)

• chebwin (needs attenuation)

• exponential (needs center, decay scale)

• tukey (needs taper fraction)

• taylor (needs number of constant sidelobes, sidelobe level)

If the window requires no parameters, then window can be a string. If the window requires parameters, then window must be a tuple with the first argument the string name of the window, and the next arguments the needed parameters.

Examples

Options for parameter shape.

>>> import pyfar as pf
>>> import numpy as np
>>> signal = pf.Signal(np.ones(100), 44100)
>>> for shape in ['symmetric', 'symmetric_zero', 'left', 'right']:
>>>     signal_windowed = pf.dsp.time_window(
...         signal, interval=[25,45], shape=shape)
>>>     ax = pf.plot.time(signal_windowed, label=shape)
>>> ax.legend(loc='right')


>>> import pyfar as pf
>>> import numpy as np
>>> signal = pf.Signal(np.ones(100), 44100)
>>> signal_windowed = pf.dsp.time_window(
...         signal, interval=[25, 40, 60, 90], window='hann')
>>> pf.plot.time(signal_windowed)

pyfar.dsp.wrap_to_2pi(x)[source]

Wraps phase to 2 pi.

Parameters:

x (double) – Input phase to be wrapped to 2 pi.

Returns:

x – Phase wrapped to 2 pi.

Return type:

double

pyfar.dsp.zero_phase(signal)[source]

Calculate zero phase signal.

The zero phase signal is obtained by taking the absolute values of the spectrum

$H_z = |H| = \sqrt{\mathrm{real}(H)^2 + \mathrm{imag}(H)^2},$

where $$H$$ is the complex valued spectrum of the input data and $$H_z$$ the real valued zero phase spectrum.

The time domain data of a zero phase signal is symmetric around the first sample, e.g., signal.time[0, 1] == signal.time[0, -1]`.

Parameters:

signal (Signal, FrequencyData) – input data

Returns:

signal – zero phase copy of the input data

Return type: