|
|
# MIT License, Copyright (c) 2020 Alexandre Défossez.
|
|
|
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
|
|
#
|
|
|
# Modified from julius(https://github.com/adefossez/julius/blob/main/tests/test_lowpass.py)
|
|
|
import math
|
|
|
import random
|
|
|
import sys
|
|
|
import unittest
|
|
|
|
|
|
import numpy as np
|
|
|
import paddle
|
|
|
|
|
|
from audio.audiotools.core import lowpass_filter
|
|
|
from audio.audiotools.core import LowPassFilter
|
|
|
from audio.audiotools.core import LowPassFilters
|
|
|
from audio.audiotools.core import resample_frac
|
|
|
|
|
|
|
|
|
def pure_tone(freq: float, sr: float=128, dur: float=4, device=None):
|
|
|
"""
|
|
|
Return a pure tone, i.e. cosine.
|
|
|
|
|
|
Args:
|
|
|
freq (float): frequency (in Hz)
|
|
|
sr (float): sample rate (in Hz)
|
|
|
dur (float): duration (in seconds)
|
|
|
"""
|
|
|
time = paddle.arange(int(sr * dur), dtype="float32") / sr
|
|
|
return paddle.cos(2 * math.pi * freq * time)
|
|
|
|
|
|
|
|
|
def delta(a, b, ref, fraction=0.9):
|
|
|
length = a.shape[-1]
|
|
|
compare_length = int(length * fraction)
|
|
|
offset = (length - compare_length) // 2
|
|
|
a = a[..., offset:offset + length]
|
|
|
b = b[..., offset:offset + length]
|
|
|
# 计算绝对差值,均值,然后除以ref的标准差,乘以100
|
|
|
return 100 * paddle.mean(paddle.abs(a - b)) / paddle.std(ref)
|
|
|
|
|
|
|
|
|
TOLERANCE = 1 # Tolerance to errors as percentage of the std of the input signal
|
|
|
|
|
|
|
|
|
class _BaseTest(unittest.TestCase):
|
|
|
def assertSimilar(self, a, b, ref, msg=None, tol=TOLERANCE):
|
|
|
self.assertLessEqual(delta(a, b, ref), tol, msg)
|
|
|
|
|
|
|
|
|
class TestLowPassFilters(_BaseTest):
|
|
|
def setUp(self):
|
|
|
paddle.seed(1234)
|
|
|
random.seed(1234)
|
|
|
|
|
|
def test_keep_or_kill(self):
|
|
|
for _ in range(10):
|
|
|
freq = random.uniform(0.01, 0.4)
|
|
|
sr = 1024
|
|
|
tone = pure_tone(freq * sr, sr=sr, dur=10)
|
|
|
|
|
|
# For this test we accept 5% tolerance in amplitude, or -26dB in power.
|
|
|
tol = 5
|
|
|
zeros = 16
|
|
|
|
|
|
# If cutoff frequency is under freq, output should be zero
|
|
|
y_killed = lowpass_filter(tone, 0.9 * freq, zeros=zeros)
|
|
|
self.assertSimilar(
|
|
|
y_killed, 0 * y_killed, tone, f"freq={freq}, kill", tol=tol)
|
|
|
|
|
|
# If cutoff frequency is under freq, output should be input
|
|
|
y_pass = lowpass_filter(tone, 1.1 * freq, zeros=zeros)
|
|
|
self.assertSimilar(
|
|
|
y_pass, tone, tone, f"freq={freq}, pass", tol=tol)
|
|
|
|
|
|
def test_same_as_downsample(self):
|
|
|
for _ in range(10):
|
|
|
x = paddle.randn([2 * 3 * 4 * 100])
|
|
|
x = paddle.ones_like(x)
|
|
|
np.random.seed(1234)
|
|
|
x = paddle.to_tensor(
|
|
|
np.random.randn(2 * 3 * 4 * 100), dtype="float32")
|
|
|
rolloff = 0.945
|
|
|
for old_sr in [2, 3, 4]:
|
|
|
y_resampled = resample_frac(
|
|
|
x, old_sr, 1, rolloff=rolloff, zeros=16)
|
|
|
y_lowpass = lowpass_filter(
|
|
|
x, rolloff / old_sr / 2, stride=old_sr, zeros=16)
|
|
|
self.assertSimilar(y_resampled, y_lowpass, x,
|
|
|
f"old_sr={old_sr}")
|
|
|
|
|
|
def test_fft_nofft(self):
|
|
|
for _ in range(10):
|
|
|
x = paddle.randn([1024])
|
|
|
freq = random.uniform(0.01, 0.5)
|
|
|
y_fft = lowpass_filter(x, freq, fft=True)
|
|
|
y_ref = lowpass_filter(x, freq, fft=False)
|
|
|
self.assertSimilar(y_fft, y_ref, x, f"freq={freq}", tol=0.01)
|
|
|
|
|
|
def test_constant(self):
|
|
|
x = paddle.ones([2048])
|
|
|
for zeros in [4, 10]:
|
|
|
for freq in [0.01, 0.1]:
|
|
|
y_low = lowpass_filter(x, freq, zeros=zeros)
|
|
|
self.assertLessEqual((y_low - 1).abs().mean(), 1e-6,
|
|
|
(zeros, freq))
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
unittest.main()
|