Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop complex signals #504

Draft
wants to merge 174 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
174 commits
Select commit Hold shift + click to select a range
85836a9
add complex argument to TImeData
Nov 22, 2022
1c3dadd
Update according to review
Nov 28, 2022
13968d4
reformat code
Nov 29, 2022
ccfd3e8
update according to review
Dec 6, 2022
5510172
Merge branch 'develop' into complex_time_signals
tluebeck Dec 6, 2022
c1e3fbf
bugfix develop merge: default comment parameter
Dec 6, 2022
f768037
implement unit testing for complex time signals
Dec 9, 2022
d957cc3
Merge branch 'develop' into complex_time_signals
tluebeck Feb 10, 2023
c73200b
bugfix develop merge, add some tests
Feb 12, 2023
8772cdc
flake8 checks
Feb 12, 2023
b103155
check FFT normalization
Feb 27, 2023
265f80f
adapt according to review
Mar 2, 2023
c8e11af
Apply suggestions from code review
tluebeck Mar 2, 2023
fbe12e2
update docstring in Signal class
tluebeck Mar 25, 2023
ace626b
update docstring in Signal class
tluebeck Mar 25, 2023
5ba9aed
Update docstring in Signal class
tluebeck Mar 25, 2023
0a3ed97
Update docstring in fft method
tluebeck Mar 25, 2023
7ae3470
return fit bins for ascending frequencies
tluebeck Mar 25, 2023
88727a3
apply circshift before pfft
tluebeck Mar 25, 2023
a185161
shift _calc_n_samples_from_frequency_data to fft submodule, and renam…
Mar 27, 2023
a3ed563
bugffix tetsts
Mar 27, 2023
94bfd8c
flake8 checks
Mar 27, 2023
cb21e8d
fix sphinx errors
Mar 27, 2023
c51b855
fix sphinx warning
Apr 3, 2023
621ea96
bugfix sphinx error
Apr 14, 2023
bef0f2d
implement time_data and signal tests
Apr 14, 2023
806b22c
implement unit testing
Apr 17, 2023
8e412ed
add complex sine and impulse stub
Apr 18, 2023
5b3a1b6
remove filter_tests
Apr 21, 2023
f9531b8
Update input checks, and implement tests
Apr 28, 2023
ede582c
adapt convolution for complex singals and implement testing
Apr 28, 2023
b3a90ae
bugfix flake
Apr 28, 2023
882dda3
implement filter tests
Apr 28, 2023
1a8cf31
Raise errors in energy, power, and rms for complex signals
Apr 28, 2023
88ae1c4
Implement dsp tests
May 3, 2023
259aba3
Update according to review
May 3, 2023
0acc2cd
Update according to review.
May 4, 2023
673fedb
Implement signal.frequency test
May 5, 2023
708e7db
check and test audio arithmetic for complex-valued data
May 11, 2023
7010945
Implement _add_mirror_spectrum and _remove_mirror_spectrum
May 16, 2023
6a17b64
Implement setter for complex property of a Signal and TimeData
May 16, 2023
8c8e1ce
add TimeData test for complex setter
May 16, 2023
ff9b81a
Merge branch 'complex_time_signals' into complex_audio_arithmetics
May 17, 2023
45a73bc
update according to reveview
May 17, 2023
414faa1
update according to review
Jun 6, 2023
24963d1
edit docstring
Jun 8, 2023
9149aa8
bugfix docstring
Jun 8, 2023
4c5652e
Merge branch 'complex_time_signals' into complex_audio_arithmetics
Jun 8, 2023
3e0e803
Update according to review
tluebeck Jun 30, 2023
011f585
update according to review
tluebeck Jun 30, 2023
e47ea0d
add test for multiplication
Jun 30, 2023
bd17351
Update according to review
tluebeck Jul 10, 2023
d29834d
Update according to review
tluebeck Jul 10, 2023
27a2d28
Update according to review
tluebeck Jul 10, 2023
1cb410b
Update according to review
tluebeck Jul 10, 2023
035944b
Update according to review
Jul 11, 2023
0842766
Update according to review
Jul 12, 2023
83ed2fc
Update according to review
Jul 17, 2023
867d1a1
test conjugate symmetry inside remove_mirror_spec method
Jul 21, 2023
66af74a
update according to review
Jul 24, 2023
8816a86
update according to review
Jul 28, 2023
6658934
update according to review
Aug 24, 2023
bc27f23
fix typo parameters
Sep 14, 2023
32b2e6b
refactoring
Sep 14, 2023
eaf98ac
update docstring
Sep 14, 2023
907d36e
Merge branch 'develop' into complex_time_signals
Sep 29, 2023
dda50ee
refactoring
Sep 29, 2023
438c4f6
Merge branch 'develop' into complex_time_signals
mberz Oct 6, 2023
a183a79
update according to review
Oct 6, 2023
9167bb4
Merge branch 'complex_time_signals' of github.com:pyfar/pyfar into co…
Oct 6, 2023
cce420f
Merge pull request #396 from pyfar/complex_time_signals
tluebeck Oct 12, 2023
4ff5a7f
Merge branch 'develop_complex_signals' into complex_audio_arithmetics
Oct 12, 2023
8f6a380
Merge branch 'develop_complex_signals' into complex_audio_arithmetics
Oct 12, 2023
07e1262
add consistent checks of input data types and values for all audio cl…
f-brinkmann Oct 12, 2023
8a05455
adapt tests to new input checks
f-brinkmann Oct 12, 2023
f4bf9f4
add new tests for _Audio class
f-brinkmann Oct 12, 2023
75a5480
add new tests for signal class
f-brinkmann Oct 12, 2023
b869c03
add new tests for TimeData class
f-brinkmann Oct 12, 2023
08e4c5d
avoid overwriting dtype complex
Oct 14, 2023
a247563
Merge branch 'develop_complex_signals' into complex_average
Oct 14, 2023
bc5b5d5
Update test_dsp_average.py
Oct 14, 2023
cca38ad
Merge pull request #459 from pyfar/complex_average
tluebeck Oct 15, 2023
5fcdc02
Merge branch 'develop_complex_signals' into complex_power_energy_rms
Oct 15, 2023
afb69a9
Update test_dsp_energy_power_rms.py
Oct 16, 2023
94e8056
Merge pull request #464 from pyfar/complex_audio_arithmetics
tluebeck Oct 16, 2023
28f0181
Merge branch 'develop_complex_signals' into complex_convolution
Oct 16, 2023
ab7873a
update complex convolution
Oct 16, 2023
f1db315
bugfix in _get_arithmetic_data
Oct 17, 2023
59c9087
change isinstance with checking for type
Oct 18, 2023
896003b
update according to review
Oct 18, 2023
669b189
Merge branch 'develop_complex_signals' into test_dsp
Oct 21, 2023
b518dd8
update test_dsp
Oct 21, 2023
25c589f
implement dsp_resampling_test for complex signals
Oct 21, 2023
53db89b
edit resampling test, check spectrogram for complex signals
Oct 22, 2023
d449be9
add dsp_phase test for complex signals
Oct 22, 2023
3beb452
edit complex setter
Oct 23, 2023
55f689c
update according to review
Oct 24, 2023
d9e1a88
bugfix flake8
Oct 24, 2023
237b31c
start implementing regularized spectrum inversion test
Oct 31, 2023
83909e9
Merge branch 'develop_complex_signals' into test_filter
Oct 31, 2023
6d1b6f2
replace single filter tests with test of the IIR and FIR base classes
Nov 1, 2023
c9278bc
add test for SOS filters
Nov 1, 2023
25e8247
Merge branch 'develop' into develop_complex_signals
Nov 1, 2023
244d4c5
implement test for signal concatenation
Nov 1, 2023
82b0d5a
kill faulty double initialization in FilterSOS tests
Nov 8, 2023
638c21e
Merge pull request #505 from pyfar/enhance/force_numeric_data_in_audi…
f-brinkmann Dec 8, 2023
2318c07
Update tests/test_filter.py
f-brinkmann Dec 15, 2023
b588cb8
Merge pull request #460 from pyfar/test_filter
f-brinkmann Dec 15, 2023
dfa3533
Merge pull request #510 from pyfar/complex_audio_arithmetics
f-brinkmann Dec 15, 2023
fce0c9e
update according to review
Dec 17, 2023
d9752c6
update tests accordingly
Dec 17, 2023
d135f8f
update according to review
Dec 17, 2023
1c994d9
Implement energy, power, and rms for complex time singals
Dec 17, 2023
f86a375
Merge pull request #513 from pyfar/complex_time_signals
tluebeck Dec 17, 2023
ed9ecbe
Merge branch 'develop_complex_signals' into test_dsp
Dec 17, 2023
992c80a
update according to review
Dec 19, 2023
cb5a19d
Merge pull request #461 from pyfar/complex_convolution
tluebeck Dec 19, 2023
158bb6e
Merge branch 'develop_complex_signals' into complex_power_energy_rms
Dec 19, 2023
9364564
allow rms fft norm
Dec 20, 2023
0d73e4e
Update dsp.py
tluebeck Dec 20, 2023
0367bd4
Merge branch 'develop_complex_signals' into test_dsp
Dec 20, 2023
e364a0a
bugfix flake 8
Dec 20, 2023
b0280e6
update average for complex signals
Dec 22, 2023
cb3bb06
Merge pull request #462 from pyfar/complex_power_energy_rms
tluebeck Feb 8, 2024
4f02aa5
Merge branch 'develop_complex_signals' into test_dsp
tluebeck Feb 9, 2024
1494cf3
Merge remote-tracking branch 'origin/develop' into develop_complex_si…
tluebeck Feb 9, 2024
b907489
Merge branch 'develop_complex_signals' into test_dsp
tluebeck Feb 9, 2024
154a1e9
merge develop_complex
tluebeck Feb 11, 2024
6ff2214
fft shift in spectogramm according to review
tluebeck Feb 20, 2024
1d1545b
flake8 bugfix
tluebeck Feb 20, 2024
456108c
flake 8 fix
tluebeck Feb 20, 2024
a15d8b2
remove mode restriction for complex signals in dsp_average
tluebeck Feb 22, 2024
02c6eb8
Merge pull request #525 from pyfar/test_dsp
tluebeck Feb 23, 2024
433dd2f
make group delay work for complex valued time signals
tluebeck Feb 25, 2024
911c5db
Merge branch 'develop_complex_signals' into test_complex_concanate
tluebeck Feb 25, 2024
9b117a8
implement test
tluebeck Feb 26, 2024
4fbdfd9
add complex parameter to test_dsp_normalize
tluebeck Feb 26, 2024
1c6e4dd
implement test
tluebeck Feb 27, 2024
ab382ef
update according to review
tluebeck Feb 27, 2024
88a0aa5
update according to review
tluebeck Feb 27, 2024
f739692
Merge pull request #546 from pyfar/complex_group_delay
tluebeck Mar 29, 2024
a8226af
Merge pull request #547 from pyfar/test_complex_concanate
tluebeck Mar 29, 2024
d1ffe15
do not allow rms and power normalization for complex time signals
tluebeck Apr 2, 2024
89b37f3
do not allow averaging of power spectra of complex time signals
tluebeck Apr 2, 2024
ed6f34a
do not allow energy, power, and rms for complex time signals
tluebeck Apr 2, 2024
8806ec0
flake8 checks
tluebeck Apr 3, 2024
3f57cf4
Merge pull request #566 from pyfar/complex_power_energy_rms
tluebeck Apr 4, 2024
5900a21
Merge branch 'develop_complex_signals' into complex_normalization
tluebeck Apr 4, 2024
6e598d7
do not allow energy, power, and rms normalization of complex time sin…
tluebeck Apr 4, 2024
aca14ba
adapt fft_norm_setter
tluebeck Apr 4, 2024
3e41353
Merge branch 'develop_complex_signals' into complex_normalization
tluebeck Apr 4, 2024
ed98642
adapt tests
tluebeck Apr 4, 2024
b06700c
flake8 checks
tluebeck Apr 4, 2024
a6bf70d
Also not allow psd fft_normalization
tluebeck Apr 5, 2024
75f594a
implement and test for complex valued time signals
tluebeck Apr 10, 2024
c858640
flake8
tluebeck Apr 10, 2024
97f677d
update according to review
tluebeck Apr 13, 2024
8117822
Merge pull request #567 from pyfar/complex_normalization
tluebeck Apr 15, 2024
9b5da33
update according to review
tluebeck Apr 15, 2024
e53d25a
Merge branch 'develop_complex_signals' into test_find_impulse_respons…
tluebeck Apr 15, 2024
0889147
implement tests
tluebeck Apr 15, 2024
4122d6b
bugfix test
tluebeck Apr 19, 2024
11fe0be
bugfix add mirror spec odd samples
tluebeck Apr 20, 2024
a7df192
update tests
tluebeck Apr 20, 2024
ef05756
Merge pull request #592 from pyfar/bugfix_set_complex_freq
mberz Apr 24, 2024
4ba3140
Merge branch 'develop_complex_signals' into complex_deconvolution
tluebeck Apr 24, 2024
9f614d9
flake8 checks
tluebeck Apr 24, 2024
ca396b4
update according to review
tluebeck Apr 25, 2024
c0b2278
Merge pull request #594 from pyfar/complex_deconvolution
tluebeck Apr 25, 2024
2430d3c
Merge pull request #573 from pyfar/test_find_impulse_response_delay_c…
tluebeck Apr 25, 2024
6000d69
implement test for fractional time shift
tluebeck May 2, 2024
af90d27
update accroding to review
tluebeck May 14, 2024
429b3d4
remove leftover
tluebeck May 14, 2024
ad03717
Merge pull request #601 from pyfar/test_complex_frac_time_shift
tluebeck May 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
315 changes: 247 additions & 68 deletions pyfar/classes/audio.py

Large diffs are not rendered by default.

172 changes: 117 additions & 55 deletions pyfar/dsp/dsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pyfar.dsp import fft
from pyfar.classes.warnings import PyfarDeprecationWarning
import warnings
import scipy.fft as sfft


def phase(signal, deg=False, unwrap=False):
Expand Down Expand Up @@ -102,9 +103,14 @@ def group_delay(signal, frequencies=None, method='fft'):
group_delay = group_delay.reshape(signal.cshape + (-1, ))

elif method == 'fft':
freq_k = fft.rfft(signal.time * np.arange(signal.n_samples),
signal.n_samples, signal.sampling_rate,
fft_norm='none')
if signal.complex:
freq_k = fft.fft(signal.time * np.arange(signal.n_samples),
signal.n_samples, signal.sampling_rate,
fft_norm='none')
else:
freq_k = fft.rfft(signal.time * np.arange(signal.n_samples),
signal.n_samples, signal.sampling_rate,
fft_norm='none')

group_delay = np.real(freq_k / signal.freq_raw)

Expand Down Expand Up @@ -276,7 +282,9 @@ def spectrogram(signal, window='hann', window_length=1024,
Returns
-------
frequencies : numpy array
Frequencies in Hz at which the magnitude spectrum was computed
Frequencies in Hz at which the magnitude spectrum was computed. For
complex valued signals, frequencies and spectrogram is arranged such
that 0 Hz bin is centered.
times : numpy array
Times in seconds at which the magnitude spectrum was computed
spectrogram : numpy array
Expand All @@ -298,16 +306,22 @@ def spectrogram(signal, window='hann', window_length=1024,

frequencies, times, spectrogram = sgn.spectrogram(
x=signal.time.squeeze(), fs=signal.sampling_rate, window=window,
noverlap=window_overlap, mode='magnitude', scaling='spectrum')
noverlap=window_overlap, mode='magnitude', scaling='spectrum',
return_onesided=not signal.complex)

# remove normalization from scipy.signal.spectrogram
spectrogram /= np.sqrt(1 / window.sum()**2)

# apply normalization from signal
if normalize:
spectrogram = fft.normalization(
spectrogram, window_length, signal.sampling_rate,
signal.fft_norm, window=window)
spectrogram, window_length, signal.sampling_rate,
signal.fft_norm, window=window)

# rearrange spectrogram and frequencies to center 0 Hz bin
if signal.complex:
frequencies = sfft.fftshift(frequencies)
spectrogram = sfft.fftshift(spectrogram, axes=0)

# scipy.signal takes the center of the DFT blocks as time stamp we take the
# beginning (looks nicer in plots, both conventions are used)
Expand Down Expand Up @@ -1171,7 +1185,8 @@ def time_shift(

if np.any(np.isnan(shifted.time)):
shifted = pyfar.TimeData(
shifted.time, shifted.times, comment=shifted.comment)
shifted.time, shifted.times, comment=shifted.comment,
is_complex=signal.complex)

return shifted

Expand All @@ -1190,7 +1205,9 @@ def find_impulse_response_delay(impulse_response, N=1):
2. By default a first order polynomial is used, as the slope of the
analytic signal should in theory be linear.

Alternatively see :py:func:`pyfar.dsp.find_impulse_response_start`.
Alternatively see :py:func:`pyfar.dsp.find_impulse_response_start`. For
complex-valued time signals, the delay is calculated separately for the
real and complex part, and its minimum value returned.

Parameters
----------
Expand Down Expand Up @@ -1238,54 +1255,65 @@ def find_impulse_response_delay(impulse_response, N=1):
n = int(np.ceil((N+2)/2))

start_samples = np.zeros(impulse_response.cshape)
modes = ['real', 'complex'] if impulse_response.complex else ['real']
start_sample = np.zeros((len(modes), 1), dtype=float)

for ch in np.ndindex(impulse_response.cshape):
# Calculate the correlation between the impulse response and its
# minimum phase equivalent. This requires a minimum phase equivalent
# in the strict sense, instead of the appriximation implemented in
# pyfar.
n_samples = impulse_response.n_samples
for idx, mode in enumerate(modes):
ir = impulse_response.time[ch]
ir = np.real(ir) if mode == 'real' else np.imag(ir)

if np.max(ir) > 1e-16:
# minimum phase warns if the input signal is not symmetric,
# which is not critical for this application
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", message="h does not appear to by symmetric",
category=RuntimeWarning)
ir_minphase = sgn.minimum_phase(
ir, n_fft=4*n_samples)

correlation = sgn.correlate(
ir,
np.pad(ir_minphase, (0, n_samples - (n_samples + 1)//2)),
mode='full')
lags = np.arange(-n_samples + 1, n_samples)

# calculate the analytic signal of the correlation function
correlation_analytic = sgn.hilbert(correlation)

# find the maximum of the analytic part of the correlation
# function and define the search range around the maximum
argmax = np.argmax(np.abs(correlation_analytic))
search_region_range = np.arange(argmax-n, argmax+n)
search_region = np.imag(
correlation_analytic[search_region_range])

# mask values with a negative gradient
mask = np.gradient(search_region, search_region_range) > 0

# fit a polygon and estimate its roots
search_region_poly = np.polyfit(
search_region_range[mask]-argmax, search_region[mask], N)
roots = np.roots(search_region_poly)

# Use only real-valued roots
if np.all(np.isreal(roots)):
root = roots[np.abs(roots) == np.min(np.abs(roots))]
start_sample[idx] = np.squeeze(lags[argmax] + root)
else:
start_sample[idx] = np.nan
warnings.warn('Starting sample not found for channel '
f'{ch}')
else:
start_sample[idx] = np.nan

# minimum phase warns if the input signal is not symmetric, which is
# not critical for this application
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", message="h does not appear to by symmetric",
category=RuntimeWarning)
ir_minphase = sgn.minimum_phase(
impulse_response.time[ch], n_fft=4*n_samples)

correlation = sgn.correlate(
impulse_response.time[ch],
np.pad(ir_minphase, (0, n_samples - (n_samples + 1)//2)),
mode='full')
lags = np.arange(-n_samples + 1, n_samples)

# calculate the analytic signal of the correlation function
correlation_analytic = sgn.hilbert(correlation)

# find the maximum of the analytic part of the correlation function
# and define the search range around the maximum
argmax = np.argmax(np.abs(correlation_analytic))
search_region_range = np.arange(argmax-n, argmax+n)
search_region = np.imag(correlation_analytic[search_region_range])

# mask values with a negative gradient
mask = np.gradient(search_region, search_region_range) > 0

# fit a polygon and estimate its roots
search_region_poly = np.polyfit(
search_region_range[mask]-argmax, search_region[mask], N)
roots = np.roots(search_region_poly)

# Use only real-valued roots
if np.all(np.isreal(roots)):
root = roots[np.abs(roots) == np.min(np.abs(roots))]
start_sample = np.squeeze(lags[argmax] + root)
else:
start_sample = np.nan
warnings.warn(f"Starting sample not found for channel {ch}")

start_samples[ch] = start_sample
start_samples[ch] = np.nanmin(start_sample)

return start_samples

Expand Down Expand Up @@ -1656,8 +1684,11 @@ def convolve(signal1, signal2, mode='full', method='overlap_add'):
res[..., :n_min-1] += res[..., -n_min+1:]
res = res[..., :n_max]

is_result_complex = True if res.dtype.kind == 'c' else False

return pyfar.Signal(
res, signal1.sampling_rate, domain='time', fft_norm=fft_norm)
res, signal1.sampling_rate, domain='time', fft_norm=fft_norm,
is_complex=is_result_complex)


def decibel(signal, domain='freq', log_prefix=None, log_reference=1,
Expand Down Expand Up @@ -1814,8 +1845,14 @@ def energy(signal):
if not isinstance(signal, pyfar.Signal):
raise ValueError(f"signal is type '{signal.__class__}'"
" but must be of type 'Signal'.")

if isinstance(signal, (pyfar.Signal, pyfar.TimeData)):
if signal.complex:
raise ValueError((
"'energy' is not implemented for complex time signals."))

# return and compute data
return np.sum(signal.time**2, axis=-1)
return np.sum(np.abs(signal.time)**2, axis=-1)


def power(signal):
Expand Down Expand Up @@ -1852,8 +1889,14 @@ def power(signal):
if not isinstance(signal, pyfar.Signal):
raise ValueError(f"signal is type '{signal.__class__}'"
" but must be of type 'Signal'.")

if isinstance(signal, (pyfar.Signal, pyfar.TimeData)):
if signal.complex:
raise ValueError((
"'power' is not implemented for complex time signals."))

# return and compute data
return np.sum(signal.time**2, axis=-1)/signal.n_samples
return np.sum(np.abs(signal.time)**2, axis=-1)/signal.n_samples


def rms(signal):
Expand Down Expand Up @@ -1889,6 +1932,11 @@ def rms(signal):
raise ValueError(f"signal is type '{signal.__class__}'"
" but must be of type 'Signal'.")

if isinstance(signal, (pyfar.Signal, pyfar.TimeData)):
if signal.complex:
raise ValueError((
"'rms' is not implemented for complex time signals."))

# return and compute data
return np.sqrt(power(signal))

Expand Down Expand Up @@ -1973,15 +2021,23 @@ def average(signal, mode='linear', caxis=None, weights=None, keepdims=False,
pyfar.TimeData)):
raise TypeError(("Input data has to be of type 'Signal', 'TimeData' "
"or 'FrequencyData'."))

if isinstance(signal, (pyfar.Signal, pyfar.TimeData)):
if signal.complex and mode == 'power':
raise ValueError((
"'power' is not implemented for complex time signals."))

if type(signal) is pyfar.TimeData and mode in (
'log_magnitude_zerophase', 'magnitude_zerophase',
'magnitude_phase', 'power',):
raise ValueError((
f"mode is '{mode}' and signal is type '{signal.__class__}'"
" but must be of type 'Signal' or 'FrequencyData'."))

if nan_policy not in ('propagate', 'omit', 'raise'):
raise ValueError("nan_policy has to be 'propagate', 'omit', or"
"'raise'.")

# check for caxis
if caxis and np.max(caxis) > len(signal.cshape):
raise ValueError(('The maximum of caxis needs to be smaller than '
Expand Down Expand Up @@ -2043,9 +2099,11 @@ def average(signal, mode='linear', caxis=None, weights=None, keepdims=False,
# return average data as pyfar object, depending on input signal type
if isinstance(signal, pyfar.Signal):
return pyfar.Signal(data, signal.sampling_rate, signal.n_samples,
signal.domain, signal.fft_norm, signal.comment)
signal.domain, signal.fft_norm, signal.comment,
signal.complex)
elif isinstance(signal, pyfar.TimeData):
return pyfar.TimeData(data, signal.times, signal.comment)
return pyfar.TimeData(data, signal.times, signal.comment,
signal.complex)
else:
return pyfar.FrequencyData(data, signal.frequencies, signal.comment)

Expand Down Expand Up @@ -2216,6 +2274,10 @@ def normalize(signal, reference_method='max', domain='time',
raise ValueError((
f"domain is '{domain}' and signal is type '{signal.__class__}'"
" but must be of type 'Signal' or 'FrequencyData'."))
if isinstance(signal, (pyfar.TimeData, pyfar.Signal)):
if signal.complex and reference_method in ['energy', 'power', 'rms']:
raise ValueError("'energy', 'power', and 'rms' reference method "
"is not implemented for complex time signals.")
if isinstance(limits, (int, float)) or len(limits) != 2:
raise ValueError("limits must be an array like of length 2.")
if tuple(limits) != (None, None) and \
Expand Down