Esempio n. 1
0
 def pazPlot(self, axes, canvas):
     #theta = np.linspace(-np.pi, np.pi, 201)
     #axes.plot(np.sin(theta), np.cos(theta), color='gray', linewidth=0.2)
     #plt.annotate("ωo", pol2cart(1, np.pi / 4))
     for i in range(0, len(self.stages_list)):
         if self.stages_list[i]:
             temp_list = []
             temp_list.append(self.sos[i])
             b, a = ss.sos2tf(temp_list)
             poles = np.roots(a)
             zeros = np.roots(b)
             axes.plot(np.real(poles),
                       np.imag(poles),
                       'Xb',
                       label='Poles' + str(i))
             axes.plot(np.real(zeros),
                       np.imag(zeros),
                       'or',
                       label='Zeros' + str(i))
     axes.axhline(y=0, color='gray', linewidth=1)
     axes.axvline(x=0, color='gray', linewidth=1)
     d = self.furthestPZ()
     axes.set_xlim(-d * 1.1, d * 1.1)
     axes.set_ylim(-d * 1.1, d * 1.1)
     axes.set_xlabel("Real")
     axes.set_ylabel("Imaginary")
     axes.legend(loc='best')
     canvas.draw()
Esempio n. 2
0
def plotSos(sos, oversample, fs, worN=2048):
    fig, ax = plt.subplots(3, 1)
    fig.set_size_inches(6, 8)
    fig.set_tight_layout(True)

    ax[0].set_title("Amplitude Response")
    ax[0].set_ylabel("Amplitude [dB]")
    ax[1].set_title("Phase Response")
    ax[1].set_ylabel("Phase [rad]")
    ax[2].set_title("Group Delay")
    ax[2].set_ylabel("Group Delay [sample]")
    ax[2].set_xlabel("Frequency [rad/sample]")

    cmap = plt.get_cmap("plasma")

    freq, h0 = signal.sosfreqz(sos, worN=worN, fs=oversample * fs)

    b, a = signal.sos2tf(sos)
    _, gd = signal.group_delay((b, a), w=worN)

    ax[0].plot(freq, ampToDecibel(h0), alpha=0.75, color="black")
    ax[0].set_ylim((-140, 5))
    ax[1].plot(freq, np.unwrap(np.angle(h0)), alpha=0.75, color="black")
    ax[2].plot(freq, gd, alpha=0.75, color="black")

    for axis in ax:
        # axis.set_xscale("log")
        axis.grid(which="both")

    plt.show()
Esempio n. 3
0
 def draw_cascade(self):
     if self.ui.chk_all_selected.isChecked():  #Graficame PLS
         if any(self.stages_list) == True:
             A = np.poly1d(1)
             B = np.poly1d(1)
             for i in range(0, len(self.stages_list)):
                 if self.stages_list[i]:
                     temp_list = []
                     temp_list.append(self.fix(self.sos[i]))
                     b, a = ss.sos2tf(temp_list)
                     b = np.poly1d(b)
                     a = np.poly1d(a)
                     B = np.polymul(B, b)
                     A = np.polymul(A, a)
             # self.sos=np.insert(self.sos[0],0,ss.tf2sos(B.c, A.c))
             # for t in range(0,len(self.stages_list)):
             #     self.stages_list[t] = False
             # self.stages_list[len(self.stages_list)-1] = True
             w = np.logspace(np.log10(1),
                             np.log10(self.furthestPZ() * 100 /
                                      (2 * np.pi)),
                             num=10000) * 2 * np.pi
             bode = ss.bode(ss.TransferFunction(B.c, A.c), w=w)
             self.axes_mag.plot(bode[0] / (2 * np.pi),
                                bode[1],
                                label='acumulada')
             self.axes_mag.set_xscale('log')
             self.axes_mag.set_xlabel('Frequency [Hz]')
             self.axes_mag.set_ylabel('Magnitude [dB]')
             self.axes_mag.minorticks_on()
             self.axes_mag.legend(loc='best')
             self.canvas_mag.draw()
     else:
         self.manage_plot()
Esempio n. 4
0
    def __init__(self, output_count, dtype, color):
        """
        Parameters
        ----------
        output_count : int
            number of channels to generate
        dtype : str or numpy.dtype or type
            data type to generate
        color : str
            coloration of noise to generate
        """
        super().__init__(output_count=output_count, dtype=dtype)
        color = color.upper()

        # initialize constants
        self._GAIN_FACTOR = 20
        # fmt: off
        self._B_PINK = np.array([0.049922035, -0.095993537, 0.050612699, -0.004408786], dtype=dtype)
        self._A_PINK = np.array([1, -2.494956002, 2.017265875, -0.522189400], dtype=dtype)
        # "Consider designing filters in ZPK format and converting directly to SOS."
        # https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.tf2sos.html
        self._SOS_EM = np.array(
            [[0.0248484318401191, -0.0430465879719351, 0.0185894592446002, 1, -1.83308400613427,
              0.833084457582538],
             [1.09742855358091, -2, 0.902572344822294, 1, -1.84861597668780, 0.849007279800584],
             [1.32049919002049, -2, 1.27062183754708, 1, -1.51266987481441, 0.958710654962438],
             [2, -1.23789744854974, 0.693137522016399, 1, -0.555672516031578, 0.356572277622976],
             [2, -0.127412449936323, 0.451198075878658, 1, -0.0464446484127454, 0.0651312831643292],
             [0.295094951044653, 0.0954033015853709, 0, 1, 0, 0]],
            dtype=dtype
        )
        # fmt: on

        # pick utilized coefficients
        if color == "PINK":
            a = self._A_PINK
            self._sos = tf2sos(b=self._B_PINK, a=self._A_PINK).astype(dtype)
            # "It is generally discouraged to convert from TF to SOS format, since doing so
            # usually will not improve numerical precision errors."
            # https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.tf2sos.html
        elif color in ["EM", "EIGENMIKE"]:
            [_, a] = sos2tf(sos=self._SOS_EM)
            self._sos = self._SOS_EM
        else:
            raise NotImplementedError(
                f'chosen noise generator color "{color}" not implemented yet.'
            )

        # initialize IIR filter delay conditions
        self._last_delays = sosfilt_zi(sos=self._sos).astype(dtype)
        # adjust in middle axis according to output count
        self._last_delays = np.repeat(
            self._last_delays[:, np.newaxis, :], repeats=output_count, axis=1
        )

        # approximate decay time to skip transient response part of IIR filter, according to [1]
        t60_samples = int(np.log(1000.0) / (1.0 - np.abs(np.roots(a)).max())) + 1
        # generate and discard long enough sequence to skip IIR transient response (decay time)
        _ = self.generate_block(block_length=t60_samples)
Esempio n. 5
0
 def get_impulse_response(self, dt=None, n=None):
     '''
     Converts the 2nd order section to a transfer function (b, a) and then computes the IR of the discrete time
     system.
     :param dt: the time delta.
     :param n: the no of samples to output, defaults to 1s.
     :return: t, y
     '''
     t, y = signal.dimpulse(signal.dlti(*signal.sos2tf(np.array(self.get_sos())),
                                        dt=1 / self.fs if dt is None else dt),
                            n=self.fs if n is None else n)
     return t, y
Esempio n. 6
0
 def escPlot(self, axes, canvas):
     for i in range(0, len(self.stages_list)):
         if self.stages_list[i]:
             temp_list = []
             temp_list.append(self.sos[i])
             b, a = ss.sos2tf(temp_list)
             H = ss.lti(b, a)
             time, resp = H.step()
             axes.plot(time, resp, label=str(i))
     axes.set_xlabel('time')
     axes.set_ylabel('Amplitude')
     axes.legend(loc='best')
     canvas.draw()
Esempio n. 7
0
 def rdgPlot(self, axes, canvas):
     for i in range(0, len(self.stages_list)):
         if self.stages_list[i]:
             temp_list = []
             temp_list.append(self.sos[i])
             b, a = ss.sos2tf(temp_list)
             w, gd = ss.group_delay((b, a))
             axes.plot(w / (2 * np.pi), gd, label=str(i))
     axes.set_xscale('log')
     axes.set_ylabel('Group delay [samples]')
     axes.set_xlabel('Frequency [Hz]')
     axes.minorticks_on()
     axes.legend(loc='best')
     canvas.draw()
def white_noize(sos):
    mean = 0
    std = 1
    num_samples = 1000
    samples = np.random.normal(mean, std, size=num_samples)
    b, a = signal.sos2tf(sos)
    y = signal.lfilter(b, a, samples)
    t = np.linspace(-1, 1, 1000)
    plt.figure
    plt.plot(t, y, 'b', alpha=0.75)
    #plt.plot(t, z, 'r--', t, z2, 'r', t, y, 'k')
    plt.legend(('noisy signal', 'lfilter, once', 'lfilter, twice', 'filtfilt'),
               loc='best')
    plt.grid(True)
    plt.show()
Esempio n. 9
0
 def fasPlot(self, axes, canvas):
     w = np.logspace(np.log10(1),
                     np.log10(self.furthestPZ() * 10 / (2 * np.pi)),
                     num=10000) * 2 * np.pi
     for i in range(0, len(self.stages_list)):
         if self.stages_list[i]:
             temp_list = []
             temp_list.append(self.sos[i])
             b, a = ss.sos2tf(temp_list)
             bode = ss.bode(ss.TransferFunction(b, a), w=w)
             axes.plot(bode[0] / (2 * np.pi), bode[2], label=str(i))
     axes.set_xscale('log')
     axes.set_xlabel('Frequency [Hz]')
     axes.set_ylabel('Phase [º]')
     axes.minorticks_on()
     axes.legend(loc='best')
     canvas.draw()
Esempio n. 10
0
    def furthestPZ(self):
        dist = []
        for i in range(0, len(self.stages_list)):

            temp_list = []
            temp_list.append(self.sos[i])
            b, a = ss.sos2tf(temp_list)

            poles = np.roots(a)
            zeros = np.roots(b)

            if len(poles):
                if len(zeros):
                    dist.append(max(max(abs(poles)), max(abs(zeros))))
                else:
                    dist.append(max(abs(poles)))
            elif len(zeros):
                dist.append(max(abs(zeros)))
        return max(dist)
Esempio n. 11
0
def gen_speech(F, bw, sig, fs):
    """
    Args : 
        F: formant frequencies (np array)
        sig: original signal to filter
        fs: sampling frequency [Hz]
    """
    nsecs = len(F)
    R = np.exp(-np.pi * bw / fs)  # pope radii
    theta = 2 * np.pi * F / fs  # pole angles
    poles = R * np.exp(1j * theta)

    A = np.real(np.poly(np.concatenate([poles, poles.conj()], axis=0)))
    B = np.zeros(A.shape)
    B[0] = 1
    r, p, f = signal.residuez(B, A)
    As = np.zeros((nsecs, 3), dtype=np.complex)
    Bs = np.zeros((nsecs, 3), dtype=np.complex)

    for idx, i in enumerate(range(1, 2 * nsecs + 1, 2)):
        j = i - 1

        Bs[idx] = [r[j] + r[j + 1], -(r[j] * p[j + 1] + r[j + 1] * p[j]), 0]
        As[idx] = [1, -(p[j] + p[j + 1]), p[j] * p[j + 1]]

    sos = np.concatenate([As, Bs], axis=1)
    iperr = np.abs(np.imag(sos)) / (np.abs(sos) + 1e-10)
    sos = np.real(sos)
    Bh, Ah = signal.sos2tf(sos)
    nfft = 512

    H = np.zeros((nsecs + 1, nfft))
    for i in range(nsecs):
        Hiw, w = signal.freqz(Bs[i, :], As[i, :])
        H[i + 1, :] = np.conj(Hiw[:])

    H[0, :] = np.sum(H[1:, :], axis=0)

    speech = signal.lfilter([1], A, sig)
    speech = speech - speech.mean()
    speech = speech / np.max(np.abs(speech))
    return speech
Esempio n. 12
0
def evTimeResponse(iir, target, ts):
    """
    Evalúa la aptitud de la respuesta temporal de un filtro
    digital IIR en punto fijo según la similitud que su
    respuesta temporal presenta respecto a la respuesta
    objetivo.

    :param iir: filtro digital IIR en punto fijo.
    :param target: respuesta al impulso objetivo.
    :param ts: período de muestreo, en segundos.
    :return: aptitud del filtro provisto.
    """
    # Computa la respuesta al impulso del filtro candidato
    # en su representación SOS.
    _, (im, ) = signal.dimpulse((*signal.sos2tf(iir2sos(iir)), ts),
                                n=len(target))
    # Computa el error relativo entre respuesta al impulso
    # del filtro candidato y respuesta al impulso esperada.
    et = (im - target) / np.max(np.abs(target))
    # Evalua la aptitud del filtro candidato como el recíproco
    # de la potencia de error relativo.
    return (1. / (np.mean(et)**2 + np.var(et)), )
Esempio n. 13
0
# scaling factor in bits
q = 14
# scaling factor as facor...
scaling_factor = 2**q

# let's generate a sequence of 2nd order IIR filters
#sos = signal.butter(2,[f1/fs*2,f2/fs*2],'pass',output='sos')
sos = signal.cheby1(3,
                    0.2, [f1 / fs * 2, f2 / fs * 2],
                    'bandpass',
                    output='sos')

sos = np.round(sos * scaling_factor)

# print coefficients
for biquad in sos:
    for coeff in biquad:
        print(int(coeff), ",", sep="", end="")
    print(q)

# plot the frequency response
b, a = signal.sos2tf(sos)
w, h = signal.freqz(b, a)
pl.plot(w / np.pi / 2 * fs, 20 * np.log(np.abs(h)))
pl.xlabel('frequency/Hz')
pl.ylabel('gain/dB')
pl.ylim(top=1, bottom=-20)
pl.xlim(left=250, right=12000)
pl.show()
def main():
    input_file = "test/LDC93S6A.wav"
    howling_file = "test/added_howling.wav"
    output_file = "test/removed_howling.wav"

    #load clean speech file
    x, Srate = sf.read(input_file)

    #pre design a room impulse response
    rir = np.loadtxt('test/path.txt', delimiter='\t')
    plt.figure()
    plt.plot(rir)

    #G : gain from mic to speaker
    G = 0.2

    # ====== set STFT parameters ========
    interval = 0.01  #frame interval = 0.02s
    Slen = int(np.floor(interval * Srate))
    if Slen % 2 == 1:
        Slen = Slen + 1
    PERC = 50  #window overlap in percent of frame size
    len1 = int(np.floor(Slen * PERC / 100))
    len2 = int(Slen - len1)
    nFFT = 2 * Slen
    freqs = np.linspace(0, Srate, nFFT)
    Nframes = int(np.floor(len(x) / len2) - np.floor(Slen / len2))

    #Hanning window for stft
    win = np.hanning(Slen)
    win = win * len2 / np.sum(win)

    plt.figure()
    plt.subplot(2, 1, 1)
    plt.plot(x)
    plt.xlim(0, len(x))
    plt.subplot(2, 1, 2)
    plt.specgram(x, NFFT=nFFT, Fs=Srate, noverlap=len2, cmap='jet')
    plt.ylim((0, 5000))
    plt.ylabel("Frquency (Hz)")
    plt.xlabel("Time (s)")

    #simulate acoustic feekback, point-by-point
    #                                    _______________                              _______________
    #   clean speech: x --> mic: x1 --> | Internal Gain | --> x2 -- > speaker : y--> | Room Impulse  |
    #                        ^          |______G________|                            |____Response___|
    #                        |                                                              |
    #                         ----------------------<-----y1--------------------------------V
    #
    N = min(2000, len(rir))  #limit room impulse response length
    x2 = np.zeros(
        N)  #buffer N samples of speaker output to generate acoustic feedback
    y = np.zeros(len(x))  #save speaker output to y
    y1 = 0.0  #init as 0
    for i in range(len(x)):
        x1 = x[i] + y1
        y[i] = G * x1
        y[i] = min(2, y[i])  #amplitude clipping
        y[i] = max(-2, y[i])
        x2[1:] = x2[:N - 1]
        x2[0] = y[i]
        y1 = np.dot(x2, rir[:N])

    sf.write(howling_file, y, Srate)
    plt.figure()
    plt.subplot(2, 1, 1)
    plt.plot(y)
    plt.xlim((0, len(y)))
    plt.subplot(2, 1, 2)
    plt.specgram(y, NFFT=nFFT, Fs=Srate, noverlap=len2, cmap='jet')
    plt.ylim((0, 5000))
    plt.ylabel("Frquency (Hz)")
    plt.xlabel("Time (s)")

    #=============================Notch Filtering =======================================================
    #                                       ___________________
    #                             -------> | Howling Detection | ______
    #                            |         |___________________|       |
    #                            |                                     |
    #                            |      _______________         _______V______
    #   clean speech: x --> mic: x1 --> | Internal Gain |-x2--> | Notch Filter | --> speaker : y
    #                        ^          |______G________|       |_____IIR______|         |
    #                        |                                                           |
    #                        |                      _______________                      |
    #                        <-----------------y1--| Room Impulse  |____________________ v
    #                                              |____Response___|
    #
    b = [1.0, 0, 0]
    a = [0, 0, 0]
    N = min(2000, len(rir))  #limit room impulse response length
    x2 = np.zeros(100)  #
    x3 = np.zeros(
        N)  #buffer N samples of speaker output to generate acoustic feedback
    y = np.zeros(len(x))  #save speaker output to y
    y1 = 0.0  #init as 0
    current_frame = np.zeros(Slen)
    pos = 0
    candidates = np.zeros([Slen, Nframes + 1], dtype='int')
    frame_id = 0
    notch_freqs = []

    for i in range(len(x)):
        x1 = x[i] + y1
        current_frame[pos] = x1
        pos = pos + 1
        if pos == Slen:
            #update notch filter frame by frame
            freq_ids = howling_detect(current_frame, win, nFFT, Slen,
                                      candidates, frame_id)
            #freq_ids = [46]
            if (len(freq_ids) > 0 and
                (len(freq_ids) != len(notch_freqs)
                 or not np.all(np.equal(notch_freqs, freqs[freq_ids])))):
                notch_freqs = freqs[freq_ids]
                sos = np.zeros([len(notch_freqs), 6])
                for i in range(len(notch_freqs)):
                    b0, a0 = signal.iirnotch(notch_freqs[i], 1, Srate)
                    sos[i, :] = np.append(b0, a0)
                b, a = signal.sos2tf(sos)
            print("frame id: ", frame_id, "/", Nframes, "notch freqs:",
                  notch_freqs)
            current_frame[:Slen - len2] = current_frame[len2:]  #shift by len2
            pos = len2
            frame_id = frame_id + 1

        x2[1:] = x2[:len(x2) - 1]
        x2[0] = G * x1
        x2[0] = min(2, x2[0])  #amplitude clipping
        x2[0] = max(-2, x2[0])
        y[i] = np.dot(x2[:len(b)], b) - np.dot(x3[:len(a) - 1],
                                               a[1:])  #IIR filter
        y[i] = min(2, y[i])  #amplitude clipping
        y[i] = max(-2, y[i])
        x3[1:] = x3[:N - 1]
        x3[0] = y[i]
        y1 = np.dot(x3, rir[:N])

    pyHowling.plot_notch_filter(b, a, Srate)
    xfinal = y
    sf.write(output_file, xfinal, Srate)

    plt.figure()
    plt.subplot(2, 1, 1)
    plt.plot(xfinal)
    plt.xlim((0, len(xfinal)))
    plt.subplot(2, 1, 2)
    plt.specgram(xfinal, NFFT=nFFT, Fs=Srate, noverlap=len2, cmap='jet')
    plt.ylim((0, 5000))
    plt.ylabel("Frquency (Hz)")
    plt.xlabel("Time (s)")
    plt.show()
Esempio n. 15
0
def test_sos2tf():
    sos_f32 = np.array([[4, 5, 6, 1, 2, 3]], dtype=np.float32)
    b, a = sos2tf(sos_f32)
    assert_(b.dtype == np.float32)
    assert_(a.dtype == np.float32)
Esempio n. 16
0

params = get_params()
while(1): # Loop until we're happy with our filter
  sos,params = design(params)

  # Plot frequency response and group delay
  ax = plt.subplot(121)
  pulse = np.zeros(1024)
  pulse[0]=1
  h = np.absolute(fft.rfft(sig.sosfilt(sos, pulse)))
  w = np.linspace(0,1,len(h))
  plt.semilogy(w,h)
  plt.title("Frequency Response")
  ax = plt.subplot(122)
  w,grp = sig.group_delay(sig.sos2tf(sos))
  w = np.linspace(0,1,len(w))
  ax.plot(w,grp)
  plt.title("Group delay")
  plt.show()
  
  x = input("Try this filter? y/n (y) ") 
  if len(x)!=0 and x[0]=='n':
    continue #pick a new filter
  
  #Plot before/after for time and frequency domain
  zi = sig.sosfilt_zi(sos) # Set initial conditions
  # Use the initial conditions to produce a sane result (not jumping from 0)
  clean, zo = sig.sosfilt(sos, data, zi=zi*data[0])
  w = np.linspace(0, nyquist, len(f))
  plt.subplot(121)
Esempio n. 17
0
def fil_convert(fil_dict, format_in):
    """
    Convert between poles / zeros / gain, filter coefficients (polynomes)
    and second-order sections and store all formats not generated by the filter
    design routine in the passed dictionary 'fil_dict'.

    Parameters
    ----------
    fil_dict :  dictionary
         filter dictionary
    
    format_in :  string or set of strings
         format(s) generated by the filter design routine. Must be one of
         'zpk': [z,p,k] where z is the array of zeros, p the array of poles and
             k is a scalar with the gain
         'ba' : [b, a] where b and a are the polynomial coefficients
         'sos' : a list of second order sections
    

    """

    if 'zpk' in format_in:  # z, p, k have been generated,convert to other formats
        zpk = fil_dict['zpk']
        if 'ba' not in format_in:
            fil_dict['ba'] = sig.zpk2tf(zpk[0], zpk[1], zpk[2])
        if 'sos' not in format_in and SOS_AVAIL:
            try:
                fil_dict['sos'] = sig.zpk2sos(zpk[0], zpk[1], zpk[2])
            except ValueError:
                fil_dict['sos'] = 'None'
                print(
                    "WARN (pyfda_lib): Complex-valued coefficients, could not convert to SOS."
                )

    elif 'sos' in format_in and SOS_AVAIL:
        if 'zpk' not in format_in:
            fil_dict['zpk'] = list(sig.sos2zpk(fil_dict['sos']))
            # check whether sos conversion has created a additional (superfluous)
            # pole and zero at the origin and delete them:
            z_0 = np.where(fil_dict['zpk'][0] == 0)[0]
            p_0 = np.where(fil_dict['zpk'][1] == 0)[0]
            if p_0 and z_0:  # eliminate z = 0 and p = 0 from list:
                fil_dict['zpk'][0] = np.delete(fil_dict['zpk'][0], z_0)
                fil_dict['zpk'][1] = np.delete(fil_dict['zpk'][1], p_0)

        if 'ba' not in format_in:
            fil_dict['ba'] = list(sig.sos2tf(fil_dict['sos']))
            # check whether sos conversion has created additional (superfluous)
            # highest order polynomial with coefficient 0 and delete them
            if fil_dict['ba'][0][-1] == 0 and fil_dict['ba'][1][-1] == 0:
                fil_dict['ba'][0] = np.delete(fil_dict['ba'][0], -1)
                fil_dict['ba'][1] = np.delete(fil_dict['ba'][1], -1)

    elif 'ba' in format_in:  # arg = [b,a]
        b, a = fil_dict['ba'][0], fil_dict['ba'][1]
        fil_dict['zpk'] = list(sig.tf2zpk(b, a))
        if SOS_AVAIL:
            try:
                fil_dict['sos'] = sig.tf2sos(b, a)
            except ValueError:
                fil_dict['sos'] = 'None'
                print(
                    "WARN (pyfda_lib): Complex-valued coefficients, could not convert to SOS."
                )

    else:
        raise ValueError("Unknown input format {0:s}".format(format_in))
    def __init__(self, output_count, dtype, color):
        """
        Parameters
        ----------
        output_count : int
            number of channels to generate
        dtype : str or numpy.dtype or type
            data type to generate
        color : str
            coloration of noise to generate
        """
        super().__init__(output_count=output_count, dtype=dtype)
        color = color.upper()

        # initialize constants
        self._GAIN_FACTOR = 20

        # fmt: off
        self._B_PINK = np.array(
            [0.049922035, -0.095993537, 0.050612699, -0.004408786],
            dtype=dtype)
        self._A_PINK = np.array([1, -2.494956002, 2.017265875, -0.522189400],
                                dtype=dtype)

        # self._SOS_EM = np.array(
        #     [[0.642831971019108, -1.285660835867235, 0.642829227003050, 1, -1.938240831077712,
        #       0.938773412606562],
        #      [1.026898172219099, -1.958636131748593, 0.936872530871250, 1, -1.998609306155938,
        #       0.998614711257974],
        #      [1.016158122207582, - 2, 0.984300636295923, 1, -1.869587739664868,
        #       0.874716652825408],
        #      [2, 0.311116397866933, 0.032678524446224, 1, -1.199612825442955, 0.209446907520534],
        #      [0.025112532019265, -0.022708413470341, 0.002848575141982, 1, -0.041478045261128,
        #       0.084631884608049]],
        #     dtype=dtype
        # )  # according to Fig. 5a (+30 dB gain) in [2]
        self._SOS_EM = np.array(
            [[
                1.005691483788964, -2, 0.994309737062589, 1,
                -1.995131712857242, 0.995141919519996
            ],
             [
                 0.053750499045410, -0.070551854851138, 0.018848551742250, 1,
                 -1.396203288062881, 0.421364316283091
             ],
             [
                 1.000002228996281, -2, 0.999998915874074, 1,
                 -1.999189537733665, 0.999193631613767
             ],
             [
                 2, -0.050188084384710, 0.000794285722317, 1,
                 -0.173018215643467, 0.053670187934911
             ],
             [
                 1.383915599293111, 1.640929515677082, 0.383739206962885, 1,
                 1.074403226778343, 0.393608533940758
             ]],
            dtype=dtype)  # according to Fig. 5b (-10 dB gain) in [2]
        # fmt: on

        # pick utilized coefficients
        if color == "PINK":
            a = self._A_PINK
            self._sos = tf2sos(b=self._B_PINK, a=self._A_PINK).astype(dtype)
            # "It is generally discouraged to convert from TF to SOS format, since doing so
            # usually will not improve numerical precision errors."
            # https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.tf2sos.html
        elif color in ["EM", "EIGENMIKE"]:
            [_, a] = sos2tf(sos=self._SOS_EM)
            self._sos = self._SOS_EM
            # "Consider designing filters in ZPK format and converting directly to SOS."
            # https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.tf2sos.html
        else:
            raise NotImplementedError(
                f'chosen noise generator color "{color}" not implemented yet.')

        # initialize IIR filter delay conditions
        self._last_delays = sosfilt_zi(sos=self._sos).astype(dtype)
        # adjust in middle axis according to output count
        self._last_delays = np.repeat(self._last_delays[:, np.newaxis, :],
                                      repeats=output_count,
                                      axis=1)

        # approximate decay time to skip transient response part of IIR filter, according to [1]
        t60_samples = int(np.log(1000.0) /
                          (1.0 - np.abs(np.roots(a)).max())) + 1
        # generate and discard long enough sequence to skip IIR transient response (decay time)
        _ = self.generate_block(block_length=t60_samples)
Esempio n. 19
0
from mosqito.functions.loudness_ecma_spain.ear_filter_design import ear_filter_design
from mosqito.functions.loudness_ecma_spain.sine_wave_generator import (
    sine_wave_generator, )

# Data import
# Threshold in quiet
from validations.loudness_ecma.input.ear_filter_ecma import (
    freq as freq_ecma,
    level as level_ecma,
)

# generate outer and middle/inner ear filter coeeficient
sos_ear = ear_filter_design()

b, a = sp_signal.sos2tf(sos_ear)

# Compute the frequency response of the filter
w, h = sp_signal.sosfreqz(sos_ear, worN=1500, fs=48000)
db = 20 * log10(np_maximum(np_abs(h), 1e-5))

# Apply filter on sine wave for test
level = []
freq = []
f = 50
while f < 20000:
    # Generate test signal
    signal, _ = sine_wave_generator(
        fs=48000,
        t=1,
        spl_value=60,
sos_q = np.around((sos - 1e-9) * 2**23) / 2**23

z, p, k = signal.sos2zpk(sos)
zq, pq, kq = signal.sos2zpk(sos_q)
plt.figure()
plt.plot(np.real(z), np.imag(z), 'o')
plt.plot(np.real(p), np.imag(p), 'x')
plt.plot(np.real(zq), np.imag(z), 'ro')
plt.plot(np.real(pq), np.imag(p), 'rx')
ucx = np.cos(np.linspace(0, 2 * np.pi, 1000))
ucy = np.sin(np.linspace(0, 2 * np.pi, 1000))
plt.plot(ucx, ucy, 'k-')
plt.axis('equal')

# Plot response
b, a = signal.sos2tf(sos)
bq, aq = signal.sos2tf(sos_q)
w, h = signal.freqz(b, a=a)
wq, hq = signal.freqz(bq, a=aq)
plt.figure()
ax1 = plt.subplot(1, 1, 1)
plt.plot(w / np.pi * Fs_in / 2, 20 * np.log10(abs(h)), 'b')
plt.plot(wq / np.pi * Fs_in / 2, 20 * np.log10(abs(hq)), 'r')
plt.title('Filter frequency response')
plt.xlabel('Frequency [radians / second]')
plt.ylabel('Amplitude [dB]')
plt.margins(0, 0.1)
plt.grid(which='both', axis='both')
plt.axvline(Fs_cut / 2, color='green')  # cutoff frequency
plt.axvline(Fs_out / 2, color='red')  # cutoff frequency
Esempio n. 21
0
# sampling rate
fs = 1000

# cutoffs
f1 = 45
f2 = 55

# scaling factor in bits
q = 14
# scaling factor as facor...
scaling_factor = 2**q

# let's generate a sequence of 2nd order IIR filters
sos = signal.butter(2,[f1/fs*2,f2/fs*2],'stop',output='sos')

sos = np.round(sos * scaling_factor)

# print coefficients
for biquad in sos:
    for coeff in biquad:
        print(int(coeff),",",sep="",end="")
    print(q)

# plot the frequency response
b,a = signal.sos2tf(sos)
w,h = signal.freqz(b,a)
pl.plot(w/np.pi/2*fs,20*np.log(np.abs(h)))
pl.xlabel('frequency/Hz');
pl.ylabel('gain/dB');
pl.show()
Esempio n. 22
0
def output_filter(B, A, name, show_response = False, impulse_resp_length = 64):

	sos = signal.tf2sos(B, A)

	if show_response:
		B, A = signal.sos2tf(sos)
		w, h = signal.freqz(B, A, worN=2**12)
		plt.title('Digital filter frequency response')
		plt.semilogx(w*fs/(2.0*np.pi), 20 * np.log10(abs(h)), label = 'A_orig')
		plt.ylim(-50, 20)
		plt.ylabel('Amplitude [dB]', color='b')
		plt.xlabel('Frequency [rad/sample]')
		plt.grid()
		plt.show()

	filter_q = 31
	max_q_adjust = 0
	for bq in range(len(sos)):
		sos[bq] /= sos[bq][3]
		abs_sum = sum(abs(sos[bq])) - 1.0
		m = int(np.ceil(np.log2(abs_sum))) #TODO check this works for -ve abs_sums 
		max_q_adjust = max(max_q_adjust, m)

	max_q_adjust -= 1	#to take advantage of 64 bit maccs
	filter_q -= max_q_adjust

	#TODO check for -ve max_q_adjust

	sos_quantised = np.zeros(np.shape(sos))

	int_max = np.int64(int32_max) / 2**max_q_adjust
	print 'const int32_t filter_coeffs_' + name + '[] = {'
	for bq in range(len(sos)):
		print '\t',
		for i in range(3):
			v = np.int32(np.float64(int_max) * sos[bq][i])
			print str(v) + ', ' ,
			sos_quantised[bq][i] = np.float64(v) / np.float64(int_max)
		for i in range(4, 6):
			v = int(np.float64(int_max) * -sos[bq][i])
			sos_quantised[bq][i] = np.float64(-v) / np.float64(int_max)
			print str(v) + ', ' ,
		print
		sos_quantised[bq][3] = 1.0
	print '};'
	print 'const uint32_t num_sections_' + name + ' = ' + str(len(sos)) + ';'
	print 'const uint32_t q_format_' + name + ' = ' + str(filter_q) + ';'


	d = np.zeros(impulse_resp_length)
	d[0] = 1

	B_q, A_q = signal.sos2tf(sos_quantised)
	filter_impulse_response =  signal.lfilter(B_q, A_q, d)


	print 'const unsigned impulse_resp_length_' + name + ' = ' + str(impulse_resp_length)+';'
	print 'const int32_t expected_ir_' + name + '[' + str(impulse_resp_length) + '] = {'
	for i in range(impulse_resp_length):
		s = str(int(int32_max * filter_impulse_response[i])) + ', '
		if i%8 == 7:
			print s
		else:
			print s,
	print '};'

	return
Esempio n. 23
0
def main():
    input_file = "test/LDC93S6A.wav"
    howling_file = "test/added_howling.wav"
    output_file = "test/removed_howling.wav"

    #load clean speech file
    x, Srate = sf.read(input_file)

    #pre design a room impulse response
    rir = np.loadtxt('test/path.txt', delimiter='\t')
    plt.figure()
    plt.plot(rir)

    #G : gain from mic to speaker
    G = 0.2

    # ====== set parameters ========
    interval = 0.02  #frame interval = 0.02s
    Slen = int(np.floor(interval * Srate))
    if Slen % 2 == 1:
        Slen = Slen + 1
    PERC = 50  #window overlap in percent of frame size
    len1 = int(np.floor(Slen * PERC / 100))
    len2 = int(Slen - len1)
    nFFT = 2 * Slen

    plt.figure()
    plt.subplot(2, 1, 1)
    plt.plot(x)
    plt.xlim(0, len(x))
    plt.subplot(2, 1, 2)
    plt.specgram(x, NFFT=nFFT, Fs=Srate, noverlap=len2, cmap='jet')
    plt.ylim((0, 5000))
    plt.ylabel("Frquency (Hz)")
    plt.xlabel("Time (s)")

    #simulate acoustic feekback, point-by-point
    #                                    _______________                              _______________
    #   clean speech: x --> mic: x1 --> | Internal Gain | --> x2 -- > speaker : y--> | Room Impulse  |
    #                        ^          |______G________|                            |____Response___|
    #                        |                                                              |
    #                         ----------------------<-----y1--------------------------------V
    #
    N = min(2000, len(rir))  #limit room impulse response length
    x2 = np.zeros(
        N)  #buffer N samples of speaker output to generate acoustic feedback
    y = np.zeros(len(x))  #save speaker output to y
    y1 = 0.0  #init as 0
    for i in range(len(x)):
        x1 = x[i] + y1
        y[i] = G * x1
        y[i] = min(2, y[i])  #amplitude clipping
        y[i] = max(-2, y[i])
        x2[1:] = x2[:N - 1]
        x2[0] = y[i]
        y1 = np.dot(x2, rir[:N])

    sf.write(howling_file, y, Srate)
    plt.figure()
    plt.subplot(2, 1, 1)
    plt.plot(y)
    plt.xlim((0, len(y)))
    plt.subplot(2, 1, 2)
    plt.specgram(y, NFFT=nFFT, Fs=Srate, noverlap=len2, cmap='jet')
    plt.ylim((0, 5000))
    plt.ylabel("Frquency (Hz)")
    plt.xlabel("Time (s)")

    #notch filter
    fs = Srate  # Sample frequency (Hz)
    f0 = 603  # Frequency to be removed from signal (Hz)
    Q = 1  # Quality factor
    # Design notch filter
    b1, a1 = signal.iirnotch(f0, Q, fs)
    sos1 = np.append(b1, a1)
    #plot_notch_filter(b1, a1, fs)

    f0 = 1745  # Frequency to be removed from signal (Hz)
    Q = 5  # Quality factor
    # Design notch filter
    b2, a2 = signal.iirnotch(f0, Q, fs)
    sos2 = np.append(b2, a2)
    #plot_notch_filter(b2, a2, fs)

    sos = np.vstack((sos1, sos2))
    b, a = signal.sos2tf(sos)
    plot_notch_filter(b, a, fs)

    #=============================Notch Filtering =======================================================
    #                                    _______________         ______________
    #   clean speech: x --> mic: x1 --> | Internal Gain |-x2--> | Notch Filter | --> speaker : y
    #                        ^          |______G________|       |_____IIR______|         |
    #                        |                                                           |
    #                        |                      _______________                      |
    #                        <-----------------y1--| Room Impulse  |____________________ v
    #                                              |____Response___|
    #

    N = min(2000, len(rir))  #limit room impulse response length
    x2 = np.zeros(len(b))  #
    x3 = np.zeros(
        N)  #buffer N samples of speaker output to generate acoustic feedback
    y = np.zeros(len(x))  #save speaker output to y
    y1 = 0.0  #init as 0
    for i in range(len(x)):
        x1 = x[i] + y1
        x2[1:] = x2[:len(x2) - 1]
        x2[0] = G * x1
        x2[0] = min(1, x2[0])  #amplitude clipping
        x2[0] = max(-1, x2[0])
        y[i] = np.dot(x2, b) - np.dot(x3[:len(a) - 1], a[1:])  #IIR filter
        x3[1:] = x3[:N - 1]
        x3[0] = y[i]
        y1 = np.dot(x3, rir[:N])

    xfinal = y
    sf.write(output_file, xfinal, Srate)

    plt.figure()
    plt.subplot(2, 1, 1)
    plt.plot(xfinal)
    plt.xlim((0, len(xfinal)))
    plt.subplot(2, 1, 2)
    plt.specgram(xfinal, NFFT=nFFT, Fs=Srate, noverlap=len2, cmap='jet')
    plt.ylim((0, 5000))
    plt.ylabel("Frquency (Hz)")
    plt.xlabel("Time (s)")
    plt.show()
Esempio n. 24
0
def group_delay(b, a=1, nfft=512, whole=False, analog=False, verbose=True,
                fs=2.*pi, sos=False, alg="scipy", n_eps=100):
    """
Calculate group delay of a discrete time filter, specified by
numerator coefficients `b` and denominator coefficients `a` of the system
function `H` ( `z`).

When only `b` is given, the group delay of the transversal (FIR)
filter specified by `b` is calculated.

Parameters
----------
b :  array_like
     Numerator coefficients (transversal part of filter)

a :  array_like (optional, default = 1 for FIR-filter)
     Denominator coefficients (recursive part of filter)

whole : boolean (optional, default : False)
     Only when True calculate group delay around
     the complete unit circle (0 ... 2 pi)

verbose : boolean (optional, default : True)
    Print warnings about frequency points with undefined group delay (amplitude = 0)
    and the time used for calculating the group delay

nfft :  integer (optional, default: 512)
     Number of FFT-points

fs : float (optional, default: fs = 2*pi)
     Sampling frequency.

alg : str (default: "scipy")
      The algorithm for calculating the group delay:
          - "scipy" The algorithm used by scipy's grpdelay,
          - "jos": The original J.O.Smith algorithm; same as in "scipy" except that
            the frequency response is calculated with the FFT instead of polyval
          - "diff": Group delay is calculated by differentiating the phase
          - "Shpakh": Group delay is calculated from second-order sections

n_eps : integer (optional, default : 100)
        Minimum value in the calculation of intermediate values before tau_g is set
        to zero.

Returns
-------
tau_g : ndarray
        group delay

w : ndarray
    angular frequency points where group delay was computed

Notes
=======

The following explanations follow [JOS]_.

**Definition and direct calculation ('diff')**

The group delay :math:`\\tau_g(\\omega)` of discrete time (DT) and continuous time
(CT) systems is the rate of change of phase with respect to angular frequency.
In the following, derivative is always meant w.r.t. :math:`\\omega`:

.. math::

    \\tau_g(\\omega)
        = -\\frac{\\partial }{\\partial \\omega}\\angle H( \\omega)
        = -\\frac{\\partial \\phi(\\omega)}{\\partial \\omega}
        = -  \\phi'(\\omega)

With numpy / scipy, the group delay can be calculated directly with

.. code-block:: python

    w, H = sig.freqz(b, a, worN=nfft, whole=whole)
    tau_g = -np.diff(np.unwrap(np.angle(H)))/np.diff(w)

The derivative can create numerical problems for e.g. phase jumps at zeros of
frequency response or when the complex frequency response becomes very small e.g.
in the stop band.

This can be avoided by calculating the group delay from the derivative of the
*logarithmic* frequency response in polar form (amplitude response and phase):

.. math::

    \\ln ( H( \\omega))
      = \\ln \\left({H_A( \\omega)} e^{j \\phi(\\omega)} \\right)
      = \\ln \\left({H_A( \\omega)} \\right) + j \\phi(\\omega)

      \\Rightarrow \\; \\frac{\\partial }{\\partial \\omega} \\ln ( H( \\omega))
      = \\frac{H_A'( \\omega)}{H_A( \\omega)} +  j \\phi'(\\omega)

where :math:`H_A(\\omega)` is the amplitude response. :math:`H_A(\\omega)` and
its derivative :math:`H_A'(\\omega)` are real-valued, therefore, the group
delay can be calculated by separating real and imginary components (and discarding
the real part):

.. math::

    \\begin{align}
    \\Re \\left\\{\\frac{\\partial }{\\partial \\omega} \\ln ( H( \\omega))\\right\\} &= \\frac{H_A'( \\omega)}{H_A( \\omega)} \\\
    \\Im \\left\\{\\frac{\\partial }{\\partial \\omega} \\ln ( H( \\omega))\\right\\} &= \\phi'(\\omega)
    \\end{align}

and hence

.. math::

      \\tau_g(\\omega) = -\\phi'(\\omega) =
      -\\Im \\left\\{ \\frac{\\partial }{\\partial \\omega}
      \\ln ( H( \\omega)) \\right\\}
      =-\\Im \\left\\{ \\frac{H'(\\omega)}{H(\\omega)} \\right\\}

Note: The last term contains the complex response :math:`H(\omega)`, not the
amplitude response :math:`H_A(\omega)`!

In the following, it will be shown that the derivative of birational functions
(like DT and CT filters) can be calculated very efficiently and from this the group
delay.


**J.O. Smith's basic algorithm for FIR filters ('scipy')**

An efficient form of calculating the group delay of FIR filters based on the
derivative of the logarithmic frequency response has been described in [JOS]_
and [Lyons]_ for discrete time systems.

A FIR filter is defined via its polyome :math:`H(z) = \\sum_k b_k z^{-k}` and has
the following derivative:

.. math::

    \\frac{\\partial }{\\partial \\omega} H(z = e^{j \\omega T})
    = \\frac{\\partial }{\\partial \\omega} \\sum_{k = 0}^N b_k e^{-j k \\omega T}
    =  -jT \\sum_{k = 0}^{N} k b_{k} e^{-j k \\omega T}
    =  -jT H_R(e^{j \\omega T})

where :math:`H_R` is the "ramped" polynome, i.e. polynome :math:`H` multiplied
with a ramp :math:`k`, yielding

.. math::

    \\tau_g(e^{j \\omega T}) = -\\Im \\left\\{ \\frac{H'(e^{j \\omega T})}
                    {H(e^{j \\omega T})} \\right\\}
                    = -\\Im \\left\\{ -j T \\frac{H_R(e^{j \\omega T})}
                    {H(e^{j \\omega T})} \\right\\}
                    = T \\, \\Re \\left\\{\\frac{H_R(e^{j \\omega T})}
                    {H(e^{j \\omega T})} \\right\\}

scipy's grpdelay directly calculates the complex frequency response
:math:`H(e^{j\\omega T})` and its ramped function at the frequency points using
the polyval function.

When zeros of the frequency response are on or near the data points of the DFT, this
algorithm runs into numerical problems. Hence, it is neccessary to check whether
the magnitude of the denominator is less than e.g. 100 times the machine eps.
In this case, :math:`\\tau_g` is set to zero.

**J.O. Smith's basic algorithm for IIR filters ('scipy')**

IIR filters are defined by

.. math::

        H(z) = \\frac {B(z)}{A(z)} = \\frac {\\sum b_k z^k}{\\sum a_k z^k},

their group delay can be calculated numerically via the logarithmic frequency
response as well.

The derivative  of :math:`H(z)` w.r.t. :math:`\\omega` is calculated using the
quotient rule and by replacing the derivatives of numerator and denominator
polynomes with their ramp functions:

.. math::

    \\begin{align}
    \\frac{H'(e^{j \\omega T})}{H(e^{j \\omega T})}
    &= \\frac{\\left(B(e^{j \\omega T})/A(e^{j \\omega T})\\right)'}{B(e^{j \\omega T})/A(e^{j \\omega T})}
    = \\frac{B'(e^{j \\omega T}) A(e^{j \\omega T}) - A'(e^{j \\omega T})B(e^{j \\omega T})}
    { A(e^{j \\omega T}) B(e^{j \\omega T})}  \\\\
    &= \\frac {B'(e^{j \\omega T})} { B(e^{j \\omega T})}
      - \\frac { A'(e^{j \\omega T})} { A(e^{j \\omega T})}
    = -j T \\left(\\frac { B_R(e^{j \\omega T})} {B(e^{j \\omega T})} - \\frac { A_R(e^{j \\omega T})} {A(e^{j \\omega T})}\\right)
    \\end{align}

This result is substituted once more into the log. derivative from above:

.. math::

    \\begin{align}
    \\tau_g(e^{j \\omega T})
    =-\\Im \\left\\{ \\frac{H'(e^{j \\omega T})}{H(e^{j \\omega T})} \\right\\}
    &=-\\Im \\left\\{
        -j T \\left(\\frac { B_R(e^{j \\omega T})} {B(e^{j \\omega T})}
                    - \\frac { A_R(e^{j \\omega T})} {A(e^{j \\omega T})}\\right)
                     \\right\\} \\\\
        &= T \\Re \\left\\{\\frac { B_R(e^{j \\omega T})} {B(e^{j \\omega T})}
                    - \\frac { A_R(e^{j \\omega T})} {A(e^{j \\omega T})}
         \\right\\}
    \\end{align}


If the denominator of the computation becomes too small, the group delay
is set to zero.  (The group delay approaches infinity when
there are poles or zeros very close to the unit circle in the z plane.)

**J.O. Smith's algorithm for CT filters**

The same process can be applied for CT systems as well: The derivative of a CT
polynome :math:`P(s)` w.r.t. :math:`\\omega` is calculated by:

.. math::

    \\frac{\\partial }{\\partial \\omega} P(s = j \\omega)
    = \\frac{\\partial }{\\partial \\omega} \\sum_{k = 0}^N c_k (j \\omega)^k
    =  j \\sum_{k = 0}^{N-1} (k+1) c_{k+1} (j \\omega)^{k}
    =  j P_R(s = j \\omega)

where :math:`P_R` is the "ramped" polynome, i.e. its `k` th coefficient is
multiplied by the ramp `k` + 1, yielding the same form as for DT systems (but
the ramped polynome has to be calculated differently).

.. math::

    \\tau_g(\\omega) = -\\Im \\left\\{ \\frac{H'(\\omega)}{H(\\omega)} \\right\\}
                     = -\\Im \\left\\{j \\frac{H_R(\\omega)}{H(\\omega)} \\right\\}
                     = -\\Re \\left\\{\\frac{H_R(\\omega)}{H(\\omega)} \\right\\}


**J.O. Smith's improved algorithm for IIR filters ('jos')**

J.O. Smith gives the following speed and accuracy optimizations for the basic
algorithm:

    * convert the filter to a FIR filter with identical phase and group delay
      (but with different magnitude response)

    * use FFT instead of polyval to calculate the frequency response

The group delay of an IIR filter :math:`H(z) = B(z)/A(z)` can also
be calculated from an equivalent FIR filter :math:`C(z)` with the same phase
response (and hence group delay) as the original filter. This filter is obtained
by the following steps:

* The zeros of :math:`A(z)` are the poles of :math:`1/A(z)`, its phase response is
  :math:`\\angle A(z) = - \\angle 1/A(z)`.

* Transforming :math:`z \\rightarrow 1/z` mirrors the zeros at the unit circle,
  correcting the negative phase response. This can be performed numerically by "flipping"
  the order of the coefficients and multiplying by :math:`z^{-N}` where :math:`N`
  is the order of :math:`A(z)`. This operation also conjugates the coefficients (?)
  which mirrors the zeros at the real axis. This effect has to be compensated,
  yielding the polynome :math:`\\tilde{A}(z)`. It is the "flip-conjugate" or
  "Hermitian conjugate" of :math:`A(z)`.

  Frequently (e.g. in the scipy and until recently in the Matlab implementation)
  the conjugate operation is omitted which gives wrong results for complex
  coefficients.

* Finally, :math:`C(z) = B(z) \\tilde{A}(z)`:

.. math::

    C(z) = B(z)\\left[ z^{-N}{A}^{*}(1/z)\\right] = B(z)\\tilde{A}(z)

where

.. math::

    \\begin{align}
    \\tilde{A}(z) &=  z^{-N}{A}^{*}(1/z) = {a}^{*}_N + {a}^{*}_{N-1}z^{-1} + \ldots + {a}^{*}_1 z^{-(N-1)}+z^{-N}\\\\
    \Rightarrow \\tilde{A}(e^{j\omega T}) &=  e^{-jN \omega T}{A}^{*}(e^{-j\omega T}) \\\\
    \\Rightarrow \\angle\\tilde{A}(e^{j\omega T}) &= -\\angle A(e^{j\omega T}) - N\omega T
    \\end{align}


In Python, the coefficients of :math:`C(z)` are calculated efficiently by
convolving the coefficients of :math:`B(z)` and  :math:`\\tilde{A}(z)`:

.. code-block:: python

    c = np.convolve(b, np.conj(a[::-1]))

where :math:`b` and :math:`a` are the coefficient vectors of the original
numerator and denominator polynomes. The actual group delay is then calculated
from the equivalent FIR filter as described above.

Calculating the frequency response with the `np.polyval(p,z)` function at the
`NFFT` frequency points along the unit circle, :math:`z = \\exp(-j \\omega)`,
seems to be numerically less robust than using the FFT for the same task, it
is also much slower.

This measure fixes already most of the problems described for narrowband IIR
filters in scipy issues [SC9310]_ and [SC1175]_. In my experience, these problems
occur for all narrowband IIR response types.

**Shpak algorithm for IIR filters**

The algorithm described above is numerically efficient but not robust for
narrowband IIR filters. Especially for filters defined by second-order sections,
it is recommended to calculate the group delay using the D. J. Shpak's algorithm.

Code is available at [ENDO5828333]_ (GPL licensed) or at [SPA]_ (MIT licensed).

This algorithm sums the group delays of the individual sections which is much
more robust as only second-order functions are involved. However, converting `(b,a)`
coefficients to SOS coefficients introduces inaccuracies.

References
```````````

.. [JOS] https://ccrma.stanford.edu/%7Ejos/fp/Numerical_Computation_Group_Delay.html or

         https://www.dsprelated.com/freebooks/filters/Numerical_Computation_Group_Delay.html

.. [Lyons] https://www.dsprelated.com/showarticle/69.php

.. [SC1175] https://github.com/scipy/scipy/issues/1175

.. [SC9310] https://github.com/scipy/scipy/issues/9310

.. [SPA] https://github.com/spatialaudio/group-delay-of-filters

.. [ENDO5828333] https://gist.github.com/endolith/5828333

.. [OCTAVE] https://sourceforge.net/p/octave/mailman/message/9298101/

Examples
--------
>>> b = [1,2,3] # Coefficients of H(z) = 1 + 2 z^2 + 3 z^3
>>> tau_g, td = pyfda_lib.grpdelay(b)
"""

    if not whole:
        nfft = 2*nfft
    w = fs * np.arange(0, nfft)/nfft  # create frequency vector

    tau_g = np.zeros_like(w)  # initialize tau_g

    # ----------------

    if alg == 'auto':
        if sos:
            alg = "shpak"
            if verbose:
                logger.info("Filter in SOS format, using Shpak algorithm for group delay.")

        elif fb.fil[0]['ft'] == 'IIR':
            alg = 'jos'  # TODO: use 'shpak' here as well?
            if verbose:
                logger.info("IIR filter, using J.O. Smith's algorithm for group delay.")
        else:
            alg = 'jos'
            if verbose:
                logger.info("FIR filter, using J.O. Smith's algorithm for group delay.")

    if sos and alg != "shpak":
        b, a = sig.sos2tf(b)

    time_0 = int(time.perf_counter() * 1e9)

    # ---------------------
    if alg == 'diff':
        w, H = sig.freqz(b, a, worN=nfft, whole=whole)
        # np.spacing(1) is equivalent to matlab "eps"
        singular = np.absolute(H) < n_eps * 10 * np.spacing(1)
        H[singular] = 0
        tau_g = -np.diff(np.unwrap(np.angle(H)))/np.diff(w)
        # differentiation returns one element less than its input
        w = w[:-1]

    # ---------------------
    elif alg == 'jos':
        b, a = map(np.atleast_1d, (b, a))  # when scalars, convert to 1-dim. arrays

        c = np.convolve(b, a[::-1])     # equivalent FIR polynome
                                        # c(z) = b(z) * a(1/z)*z^(-oa)
        cr = c * np.arange(c.size)      # multiply with ramp -> derivative of c wrt 1/z

        den = np.fft.fft(c, nfft)   # FFT = evaluate polynome around unit circle
        num = np.fft.fft(cr, nfft)  # and ramped polynome at NFFT equidistant points

        # Check for singularities i.e. where denominator (`den`) coefficients
        # approach zero or numerator (`num`) or denominator coefficients are
        # non-finite, i.e. `nan`, `ìnf` or `ninf`.
        singular = np.where(~np.isfinite(den) | ~np.isfinite(num) |
                            (abs(den) < n_eps * np.spacing(1)))[0]

        with np.errstate(invalid='ignore', divide='ignore'):
            # element-wise division of numerator and denominator FFTs
            tau_g = np.real(num / den) - a.size + 1

        # set group delay = 0 at each singularity
        tau_g[singular] = 0

        if verbose and np.any(singular):
            logger.warning('singularity -> setting to 0 at:')
            for i in singular:
                logger.warning('i = {0} '.format(i * fs/nfft))

        if not whole:
            nfft = nfft/2
            tau_g = tau_g[0:nfft]
            w = w[0:nfft]

        # if analog: # doesnt work yet
        #     a_b = np.convolve(a,b)
        #     if ob > 1:
        #         br_a = np.convolve(b[1:] * np.arange(1,ob), a)
        #     else:
        #         br_a = 0
        #     ar_b = np.convolve(a[1:] * np.arange(1,oa), b)

        #     num = np.fft.fft(ar_b - br_a, nfft)
        #     den = np.fft.fft(a_b,nfft)

    # ---------------------
    elif alg == "scipy":  # implementation as in scipy.signal
        w = np.atleast_1d(w)
        b, a = map(np.atleast_1d, (b, a))
        c = np.convolve(b, a[::-1])    # coefficients of equivalent FIR polynome
        cr = c * np.arange(c.size)     # and of the ramped polynome
        z = np.exp(-1j * w)            # complex frequency points around the unit circle
        den = np.polyval(c[::-1], z)   # evaluate polynome
        num = np.polyval(cr[::-1], z)  # and ramped polynome

        # Check for singularities i.e. where denominator (`den`) coefficients
        # approach zero or numerator (`num`) or denominator coefficients are
        # non-finite, i.e. `nan`, `ìnf` or `ninf`.
        singular = np.where(~np.isfinite(den) | ~np.isfinite(num) |
                            (abs(den) < n_eps * np.spacing(1)))[0]

        with np.errstate(invalid='ignore', divide='ignore'):
            # element-wise division of numerator and denominator FFTs
            tau_g = np.real(num / den) - a.size + 1

        # set group delay = 0 at each singularity
        tau_g[singular] = 0

        if verbose and np.any(singular):
            logger.warning('singularity -> setting to 0 at:')
            for i in singular:
                logger.warning('i = {0} '.format(i * fs/nfft))

        if not whole:
            nfft = nfft/2
            tau_g = tau_g[0:nfft]
            w = w[0:nfft]

    # ---------------------
    elif alg.lower() == "shpak":  # Use Shpak's algorith
        if sos:
            w, tau_g = sos_group_delayz(b, w, fs=fs)
        else:
            w, tau_g = group_delayz(b, a, w, fs=fs)

    else:
        logger.error('Unknown algorithm "{0}"!'.format(alg))
        tau_g = np.zeros_like(w)

    time_1 = int(time.perf_counter() * 1e9)
    delta_t = time_1 - time_0
    if verbose:
        logger.info(
            "grpdelay calculation ({0}): {1:.3g} ms".format(alg, delta_t/1.e6))
    return w, tau_g
Esempio n. 25
0
def output_filter(B, A, name, show_response = False, emit_test_ir = False, impulse_resp_length = 64):

	sos = signal.tf2sos(B, A)

	if show_response:
		B, A = signal.sos2tf(sos)
		w, h = signal.freqz(B, A, worN=2**12)
		plt.title('Digital filter frequency response')
		plt.semilogx(w*fs/(2.0*np.pi), 20 * np.log10(abs(h)), label = 'A_orig')
		plt.ylim(-50, 20)
		plt.ylabel('Amplitude [dB]', color='b')
		plt.xlabel('Frequency [rad/sample]')
		plt.grid()
		plt.show()

	filter_q = 31
	max_q_adjust = 0
	for bq in range(len(sos)):
		sos[bq] /= sos[bq][3]
		abs_sum = sum(abs(sos[bq])) - 1.0
		m = int(np.ceil(np.log2(abs_sum))) #TODO check this works for -ve abs_sums 
		max_q_adjust = max(max_q_adjust, m)

	max_q_adjust -= 1	#to take advantage of 64 bit maccs
	filter_q -= max_q_adjust

	#TODO check for -ve max_q_adjust

	sos_quantised = np.zeros(np.shape(sos))

	int_max = np.int64(int32_max) / 2**max_q_adjust
	print 'const int32_t filter_coeffs_' + name + '[] = {'
	for bq in range(len(sos)):
		print '\t',
		for i in range(3):
			v = np.int32(np.float64(int_max) * sos[bq][i])
			print str(v) + ', ' ,
			sos_quantised[bq][i] = np.float64(v) / np.float64(int_max)
		for i in range(4, 6):
			v = int(np.float64(int_max) * -sos[bq][i])
			sos_quantised[bq][i] = np.float64(-v) / np.float64(int_max)
			print str(v) + ', ' ,
		print
		sos_quantised[bq][3] = 1.0
	print '};'
	print 'const uint32_t num_sections_' + name + ' = ' + str(len(sos)) + ';'
	print 'const uint32_t q_format_' + name + ' = ' + str(filter_q) + ';'
	print 'int32_t filter_state_'+ name +'[' +str(len(sos)) + '*DSP_NUM_STATES_PER_BIQUAD] = {0};'
	d = np.zeros(impulse_resp_length)
	d[0] = 1

	B_q, A_q = signal.sos2tf(sos_quantised)
	filter_impulse_response =  signal.lfilter(B_q, A_q, d)

	if emit_test_ir:
		print 'const unsigned impulse_resp_length_' + name + ' = ' + str(impulse_resp_length)+';'
		print 'const int32_t expected_ir_' + name + '[' + str(impulse_resp_length) + '] = {'
		for i in range(impulse_resp_length):
			s = str(int(int32_max * filter_impulse_response[i])) + ', '
			if i%8 == 7:
				print s
			else:
				print s,
		print '};'

	return