指数扫描正弦失真

信息处理 fft 声音的 Python 卷积 失真
2022-02-22 13:20:59

我能够在使用指数扫描正弦波的基本测量上获得可及的结果。现在我试图从相同的测量中获取失真信息,但对结果感到困惑。我预计未削波信号的失真值要低得多。这些会被认为是有效的结果吗?

未裁剪的时域测量\脉冲响应\窗口: 未剪辑

裁剪时域测量\脉冲响应\窗口: 剪裁

频域数据: 频域

以下是获取这些图表的代码:

import numpy as np
import scipy.signal as sig
import matplotlib.pyplot as plt


class SweptSineMeas(object):
    def __init__(self, duration, sample_rate, freq_start, freq_stop):
        self.duration = duration
        self.sample_rate = sample_rate
        self.freq_start = freq_start
        self.freq_stop = freq_stop
        self.sample_points = np.arange(0, self.duration, 1 / self.sample_rate)
        self.sweep_rate = np.log(self.freq_stop / self.freq_start)

    @property
    def stimulus(self):
        log_swept_sine = np.sin(
            (2 * np.pi * self.freq_start * self.duration / self.sweep_rate)
            * (np.exp(self.sample_points * self.sweep_rate / self.duration) - 1)
        )
        return log_swept_sine

    @property
    def inverse_filter(self):
        decay_map = np.exp(self.sample_points * self.sweep_rate / self.duration) * 10
        inverse_filter = self.stimulus[::-1] / decay_map
        return inverse_filter

    def _impulse_reponse(self, meas, inverse_filter):
        z = np.zeros((meas.size - inverse_filter.size))
        inverse_filter = np.concatenate((inverse_filter, z))
        impulse_response = sig.fftconvolve(meas, inverse_filter, mode="same")
        return impulse_response

    def _window(
        self,
        points,
        signal_index=None,
        start_time: float = -0.05,
        stop_time: float = 0.1,
        window="hann",
        start_percent=10,
        end_percent=10,
    ) -> np.array:
        if signal_index is None:
            signal_index = int(points / 2)
        start_skirt_points = abs(int(start_time / (1 / self.sample_rate)))
        end_skirt_points = int(stop_time / (1 / self.sample_rate))
        window_points = start_skirt_points + end_skirt_points

        start_skirt = np.zeros(signal_index - start_skirt_points)
        start_window_points = int(window_points * (start_percent / 100))
        start_window = sig.windows.get_window(window, start_window_points * 2)
        start_window = start_window[:start_window_points]

        end_skirt = np.zeros(points - signal_index - end_skirt_points)
        end_window_points = int(window_points * (end_percent / 100))
        end_window = sig.windows.get_window(window, end_window_points * 2)
        end_window = end_window[end_window_points - 1 :: -1]

        middle_window = np.ones(window_points - (start_window.size + end_window.size))
        return np.concatenate((start_skirt, start_window, middle_window, end_window, end_skirt))

    def spectrum_mag(self, meas, window_start, window_stop, plot=False):
        impulse_response = self._impulse_reponse(meas, self.inverse_filter)
        meas_points = np.arange(0, meas.size / self.sample_rate, 1 / self.sample_rate)
        ir_points = np.arange(0, impulse_response.size / self.sample_rate, 1 / self.sample_rate)
        window = self._window(impulse_response.size, start_time=window_start, stop_time=window_stop)

        if plot is True:
            plt.subplot(2, 1, 1)
            plt.grid()
            plt.plot(meas_points, meas)
            plt.subplot(2, 1, 2)
            plt.grid()
            plt.plot(ir_points, impulse_response)
            plt.twinx()
        plt.plot(ir_points, window)

        windowed_meas = impulse_response * window
        mag = np.fft.rfft(windowed_meas)
        freq = np.fft.rfftfreq(windowed_meas.size, 1 / self.sample_rate)

        return freq, 20 * np.log10(np.abs(mag))


if __name__ == "__main__":
    fund_window_start = -0.05
    fund_window_stop = 0.3
    dst_window_start = -0.4
    dst_window_stop = -0.05

    ssm = SweptSineMeas(1, 48000, 10, 10000)
    stim = ssm.stimulus
    meas = stim
    fig = plt.figure()
    fig.suptitle("unclipped")
    freq, fnd_raw = ssm.spectrum_mag(meas, fund_window_start, fund_window_stop, plot=True)
    freq, dst_raw = ssm.spectrum_mag(meas, dst_window_start, dst_window_stop)
    meas = np.clip(stim, -0.5, 0.5)
    fig = plt.figure()
    fig.suptitle("clipped")
    freq, fnd_clipped = ssm.spectrum_mag(meas, fund_window_start, fund_window_stop, plot=True)
    freq, dst_clipped = ssm.spectrum_mag(meas, dst_window_start, dst_window_stop)
    plt.figure()
    plt.grid()
    plt.semilogx(freq, fnd_raw, "-r")
    plt.semilogx(freq, dst_raw, "--r")
    plt.semilogx(freq, fnd_clipped, "-g")
    plt.semilogx(freq, dst_clipped, "--g")
    plt.ylim([-18, 66])
    plt.show()
1个回答

您好 NatanBackwards,欢迎来到 DSP SE。

你的结果对我来说似乎是有效的,但我想在这里提到几件事。

首先,您必须记住,由于“对数正弦扫描”的工作方式,脉冲响应包含失真分量之前的部分。从您处理测量的方式来看,我相信您已经知道了,但是您必须记住,根据本文,实际脉冲中可能存在一些低频失真分量(奇数序)的“泄漏”回复。这可能会导致您的结果与预期的结果有些偏差。此外,您必须记住,该方法在大多数情况下会引入一些预振铃(以及后振铃)。有关这方面的更多信息以及可能的(部分)“补救措施”可以在Angelo Farina的这篇文章中找到。

这里要提到的主要一点是你应该非常小心你的脉冲响应窗口宽度。一个可能“足够长”的失真脉冲响应(第一个失真分量的脉冲响应)可能会从“反因果”窗口泄漏到“因果”脉冲响应窗口(在 Farina 之后使用术语因果和反因果也是该方法的发明者,不一定与时间轴的开始有关)。类似地,脉冲响应的预振铃可能会泄漏到失真脉冲响应窗口中,这将导致来自脉冲响应的能量被视为失真。

除此之外,还有两条简短的评论是:

  1. 如果不进一步了解被测设备 (DUT),我们无法真正得出您提供的结果的“有效性”的结论。
  2. 在任何情况下,明显干净的测量响应都不能保证无失真的传递函数。绘图工具/设备的局限性以及用户无法注意到绘图功能的细微差异(我绝不是在暗示您有某种残疾,我只是指主观能力人类在视觉上区分小的差异)是预期中可能出现的错误的来源。您可以尝试在测量频谱旁边绘制正弦波,并通过增加正弦波的幅度来尝试查看在哪个点您将能够区分时域信号不再是正弦波。

最后,我想提一下,您的结果在一般意义上似乎相当有效,并且通过更改细节中的一些东西(例如更好地照顾窗口宽度或位置,可能的淡入/淡出)输入信号以及带宽)可能会或可能不会在结果中提供任何显着变化。如果您碰巧就您的“问题”得出任何有根据的结论,我将很高兴阅读您的更新(这也将使社区受益)。