251 lines
8.4 KiB
Python
251 lines
8.4 KiB
Python
# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
import itertools
|
|
import unittest
|
|
|
|
from parameterized import parameterized
|
|
|
|
import paddle
|
|
|
|
|
|
def parameterize(*params):
|
|
return parameterized.expand(list(itertools.product(*params)))
|
|
|
|
|
|
class TestAudioFunctions(unittest.TestCase):
|
|
def setUp(self):
|
|
paddle.disable_static()
|
|
self.initParams()
|
|
|
|
def initParams(self):
|
|
def get_wav_data(dtype: str, num_channels: int, num_frames: int):
|
|
dtype_ = getattr(paddle, dtype)
|
|
base = paddle.linspace(-1.0, 1.0, num_frames, dtype=dtype_) * 0.1
|
|
data = base.tile([num_channels, 1])
|
|
return data
|
|
|
|
self.n_fft = 512
|
|
self.hop_length = 128
|
|
self.n_mels = 40
|
|
self.n_mfcc = 20
|
|
self.fmin = 0.0
|
|
self.window_str = 'hann'
|
|
self.pad_mode = 'reflect'
|
|
self.top_db = 80.0
|
|
self.duration = 0.5
|
|
self.num_channels = 1
|
|
self.sr = 16000
|
|
self.dtype = "float32"
|
|
self.window_size = 1024
|
|
waveform_tensor = get_wav_data(
|
|
self.dtype,
|
|
self.num_channels,
|
|
num_frames=int(self.duration * self.sr),
|
|
)
|
|
self.waveform = waveform_tensor.numpy()
|
|
|
|
def convert_tensor_encoding(
|
|
self,
|
|
tensor: paddle.Tensor,
|
|
dtype: paddle.dtype,
|
|
):
|
|
"""Convert input tensor with values between -1 and 1 to integer encoding
|
|
Args:
|
|
tensor: input tensor, assumed between -1 and 1
|
|
dtype: desired output tensor dtype
|
|
Returns:
|
|
Tensor: shape of (n_channels, sample_rate * duration)
|
|
"""
|
|
if dtype == paddle.int32:
|
|
tensor *= (tensor > 0) * 2147483647 + (tensor < 0) * 2147483648
|
|
if dtype == paddle.int16:
|
|
tensor *= (tensor > 0) * 32767 + (tensor < 0) * 32768
|
|
if dtype == paddle.uint8:
|
|
tensor *= (tensor > 0) * 127 + (tensor < 0) * 128
|
|
tensor += 128
|
|
tensor = tensor.astype(dtype)
|
|
return tensor
|
|
|
|
def get_whitenoise(
|
|
self,
|
|
*,
|
|
sample_rate: int = 16000,
|
|
duration: float = 1, # seconds
|
|
n_channels: int = 1,
|
|
seed: int = 0,
|
|
dtype: str | paddle.dtype = "float32",
|
|
channels_first=True,
|
|
scale_factor: float = 1,
|
|
):
|
|
"""Generate pseudo audio data with whitenoise
|
|
Args:
|
|
sample_rate: Sampling rate
|
|
duration: Length of the resulting Tensor in seconds.
|
|
n_channels: Number of channels
|
|
seed: Seed value used for random number generation.
|
|
Note that this function does not modify global random generator state.
|
|
dtype: paddle dtype
|
|
device: device
|
|
channels_first: whether first dimension is n_channels
|
|
scale_factor: scale the Tensor before clamping and quantization
|
|
Returns:
|
|
Tensor: shape of (n_channels, sample_rate * duration)
|
|
"""
|
|
if isinstance(dtype, str):
|
|
dtype = getattr(paddle, dtype)
|
|
if dtype not in [
|
|
paddle.float64,
|
|
paddle.float32,
|
|
paddle.int32,
|
|
paddle.int16,
|
|
paddle.uint8,
|
|
]:
|
|
raise NotImplementedError(f"dtype {dtype} is not supported.")
|
|
|
|
paddle.seed(seed)
|
|
tensor = paddle.randn(
|
|
[n_channels, int(sample_rate * duration)], dtype=paddle.float32
|
|
)
|
|
tensor /= 2.0
|
|
tensor *= scale_factor
|
|
tensor.clip_(-1.0, 1.0)
|
|
if not channels_first:
|
|
tensor = tensor.T
|
|
|
|
return self.convert_tensor_encoding(tensor, dtype)
|
|
|
|
@parameterize(
|
|
["sinc_interp_hann", "sinc_interp_kaiser"],
|
|
[16000, 44100],
|
|
)
|
|
def test_resample_identity(self, resampling_method, sample_rate):
|
|
"""When sampling rate is not changed, the transform returns an identical Tensor"""
|
|
waveform = self.get_whitenoise(sample_rate=sample_rate, duration=1)
|
|
|
|
resampled = paddle.audio.functional.resample(
|
|
waveform,
|
|
sample_rate,
|
|
sample_rate,
|
|
resampling_method=resampling_method,
|
|
)
|
|
assert paddle.allclose(waveform, resampled)
|
|
|
|
@parameterize([("sinc_interp_hann"), ("sinc_interp_kaiser")])
|
|
def test_resample_waveform_downsample_size(self, resampling_method):
|
|
sr = 16000
|
|
waveform = self.get_whitenoise(
|
|
sample_rate=sr,
|
|
duration=0.5,
|
|
)
|
|
downsampled = paddle.audio.functional.resample(
|
|
waveform, sr, sr // 2, resampling_method=resampling_method
|
|
)
|
|
assert downsampled.shape[-1] == waveform.shape[-1] // 2
|
|
|
|
@parameterize([("sinc_interp_hann"), ("sinc_interp_kaiser")])
|
|
def test_resample_waveform_upsample_size(self, resampling_method):
|
|
sr = 16000
|
|
waveform = self.get_whitenoise(
|
|
sample_rate=sr,
|
|
duration=0.5,
|
|
)
|
|
downsampled = paddle.audio.functional.resample(
|
|
waveform, sr, sr * 2, resampling_method=resampling_method
|
|
)
|
|
assert downsampled.shape[-1] == waveform.shape[-1] * 2
|
|
|
|
@parameterize([("sinc_interp_hann"), ("sinc_interp_kaiser")])
|
|
def test_resample_waveform_identity_shape(self, resampling_method):
|
|
sr = 16000
|
|
waveform = self.get_whitenoise(
|
|
sample_rate=sr,
|
|
duration=0.5,
|
|
)
|
|
resampled = paddle.audio.functional.resample(
|
|
waveform, sr, sr, resampling_method=resampling_method
|
|
)
|
|
assert resampled.shape[-1] == waveform.shape[-1]
|
|
|
|
@parameterize([0, -8000, 114.514])
|
|
def test_resample_exceptions_sr_no_positive(self, sample_rate):
|
|
|
|
waveform = self.get_whitenoise(
|
|
sample_rate=16000, duration=0.5
|
|
) # shape: [1, 8000]
|
|
|
|
with self.assertRaises(ValueError) as context:
|
|
paddle.audio.functional.resample(waveform, 16000, sample_rate)
|
|
self.assertIn(
|
|
"integer",
|
|
str(context.exception),
|
|
)
|
|
|
|
@parameterize([8000])
|
|
def test_resample_exceptions_data_no_float(self, sample_rate):
|
|
|
|
waveform = self.get_whitenoise(
|
|
sample_rate=sample_rate, duration=0.5
|
|
) # shape: [1, 8000]
|
|
|
|
waveform_int = waveform.astype(paddle.int16)
|
|
with self.assertRaises(TypeError) as context:
|
|
paddle.audio.functional.resample(
|
|
waveform_int, sample_rate, sample_rate // 2
|
|
)
|
|
self.assertIn("floating point", str(context.exception))
|
|
|
|
@parameterize(["invalid_method", "invalid_method2"])
|
|
def test_resample_exceptions_invalid_method(self, resampling_method):
|
|
|
|
waveform = self.get_whitenoise(
|
|
sample_rate=16000, duration=0.5
|
|
) # shape: [1, 8000]
|
|
|
|
with self.assertRaises(ValueError) as context:
|
|
paddle.audio.functional.resample(
|
|
waveform, 16000, 8000, resampling_method=resampling_method
|
|
)
|
|
self.assertIn("Invalid resampling method", str(context.exception))
|
|
|
|
@parameterize([0, -5])
|
|
def test_resample_exceptions_filter_width_not_positive(
|
|
self, lowpass_filter_width
|
|
):
|
|
waveform = self.get_whitenoise(
|
|
sample_rate=16000, duration=0.5
|
|
) # shape: [1, 8000]
|
|
with self.assertRaises(ValueError) as context:
|
|
paddle.audio.functional.resample(
|
|
waveform, 16000, 8000, lowpass_filter_width=lowpass_filter_width
|
|
)
|
|
self.assertIn(
|
|
"Low pass filter width should be positive", str(context.exception)
|
|
)
|
|
|
|
with self.assertRaises(ValueError) as context:
|
|
paddle.audio.functional.resample(
|
|
waveform, 16000, 8000, lowpass_filter_width=lowpass_filter_width
|
|
)
|
|
self.assertIn(
|
|
"Low pass filter width should be positive", str(context.exception)
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|