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

Add support for zero phase peaking filter #303

Open
iver56 opened this issue Sep 7, 2023 · 2 comments
Open

Add support for zero phase peaking filter #303

iver56 opened this issue Sep 7, 2023 · 2 comments

Comments

@iver56
Copy link
Owner

iver56 commented Sep 7, 2023

The peaking filter we have today changes the phase. It would be nice to have a bool argument for making it zero-phase.

@atamazian
Copy link
Contributor

Can you elaborate?

@iver56
Copy link
Owner Author

iver56 commented Nov 4, 2024

Certainly.

Peaking filters adjust the gain of specific frequency bands, and are currently implemented using filters that introduce a non-linear phase shift. The phase change occurs because these filters have a frequency-dependent phase response.

It is possible to plot the phase shift for each frequency, e.g. like this:

import math
from typing import Callable, Iterable, List

import fast_align_audio
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm

from audiomentations import PeakingFilter


def get_phase_shifts_deg(
    filter_func: Callable, frequencies: Iterable, sample_rate: int
) -> List[np.float64]:
    T = 1 / sample_rate
    t = np.arange(0, 1, T)  # 1 second worth of samples

    phase_shifts = []

    for freq in tqdm(frequencies):
        sinusoid = np.sin(2 * np.pi * freq * t).astype(np.float32)

        output = filter_func(sinusoid)

        output /= np.amax(output)

        max_offset_samples = 1 + int(math.ceil(0.5 * sample_rate / freq))
        lag, _ = fast_align_audio.find_best_alignment_offset(
            reference_signal=output,
            delayed_signal=sinusoid,
            max_offset_samples=max_offset_samples,
        )

        # Convert lag to phase shift in degrees
        phase_shift = lag * T * freq * 360  # in degrees

        phase_shifts.append(phase_shift)

    return phase_shifts


if __name__ == "__main__":
    sample_rate = 44100

    # Create instance of PeakingFilter with fixed parameters
    peaking_filter = PeakingFilter(
        min_gain_db=6.0,
        max_gain_db=6.0,
        min_center_freq=1000.0,
        max_center_freq=1000.0,
        min_q=1.0,
        max_q=1.0,
        p=1.0,
    )

    frequencies = np.linspace(20, 18000, 600)

    filter_func = lambda x: peaking_filter(x, sample_rate=sample_rate)

    phase_shifts = get_phase_shifts_deg(filter_func, frequencies, sample_rate)

    plt.figure()
    plt.semilogx(frequencies, phase_shifts)
    plt.title("Peaking Filter Phase Response")
    plt.xlabel("Frequency (Hz)")
    plt.ylabel("Phase Shift (degrees)")
    plt.grid()
    plt.show()

(based on https://github.com/nomonosound/log-wmse-audio-quality/blob/master/dev/plot_filter_phase_shift.py)

bilde

As we can see, it is not a zero-phase filter. A flat curve here would mean that the filter did not affect the phase.

Achieving zero-phase filtering is sometimes done by applying the filter forward and then backward in time, effectively canceling out any phase shifts. This approach doubles the filtering computation but preserves the waveform's original phase characteristics. If the filter is FIR filter, it is possible to design the filter kernel in a symmetric way that results in a linear phase response.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants