diff --git a/python/paddle/__init__.py b/python/paddle/__init__.py index a9d3b4f02ec498..6c4c7b392921e9 100644 --- a/python/paddle/__init__.py +++ b/python/paddle/__init__.py @@ -228,6 +228,13 @@ def new_init(self, *args, **kwargs): is_autocast_enabled, ) from .amp.auto_cast import autocast +from .audio.functional.window import ( # noqa: F401 + bartlett_window, + blackman_window, + hamming_window, + hann_window, + kaiser_window, +) from .autograd import ( enable_grad, grad, diff --git a/python/paddle/audio/functional/window.py b/python/paddle/audio/functional/window.py index d0725ea9b7a741..137ec7fca887ed 100644 --- a/python/paddle/audio/functional/window.py +++ b/python/paddle/audio/functional/window.py @@ -13,6 +13,7 @@ from __future__ import annotations import math +import warnings from typing import TYPE_CHECKING import numpy as np @@ -21,9 +22,17 @@ if TYPE_CHECKING: from paddle import Tensor + from paddle._typing import PlaceLike from ..features.layers import _WindowLiteral +from paddle.base.framework import ( + _current_expected_place, + _get_paddle_place, + core, + in_dynamic_or_pir_mode, +) + class WindowFunctionRegister: def __init__(self): @@ -445,3 +454,287 @@ def get_window( params = (win_length, *args) kwargs = {'sym': sym} return winfunc(*params, dtype=dtype, **kwargs) + + +def _apply_window_postprocess( + w: Tensor, + *, + layout: str | None = None, + device: PlaceLike | None = None, + pin_memory: bool = False, + requires_grad: bool = False, +) -> Tensor: + if layout is not None: + warnings.warn("layout only supports 'strided' in Paddle; ignored") + + if in_dynamic_or_pir_mode(): + device = ( + _get_paddle_place(device) + if device is not None + else _current_expected_place() + ) + if ( + pin_memory + and paddle.in_dynamic_mode() + and device is not None + and not isinstance( + device, (core.CUDAPinnedPlace, core.XPUPinnedPlace) + ) + ): + if isinstance(device, core.CUDAPlace) or ( + isinstance(device, core.Place) and device.is_gpu_place() + ): + device = core.CUDAPinnedPlace() + elif isinstance(device, core.XPUPlace) or ( + isinstance(device, core.Place) and device.is_xpu_place() + ): + device = core.XPUPinnedPlace() + else: + raise RuntimeError( + f"Pinning memory is not supported for {device}" + ) + w = w.to(device=device) + if pin_memory and paddle.in_dynamic_mode(): + w = w.pin_memory() + if requires_grad is True: + w.stop_gradient = False + return w + + +def hamming_window( + window_length: int, + periodic: bool = True, + alpha: float = 0.54, + beta: float = 0.46, + *, + dtype: str = 'float64', + layout: str | None = None, + device: PlaceLike | None = None, + pin_memory: bool = False, + requires_grad: bool = False, +): + """ + Compute a generalized Hamming window. + + Args: + window_length (int): The size of the returned window. Must be positive. + periodic (bool, optional): If True, returns a window for use as a periodic function; if False, returns a symmetric window. Defaults to True. + alpha (float, optional): The coefficient α in the equation above. Defaults to 0.54. + beta (float, optional): The coefficient β in the equation above. Defaults to 0.46. + dtype (str, optional): The data type of the returned tensor. Defaults to 'float64'. + layout (str, optional): Only included for API consistency with PyTorch; ignored in Paddle. Defaults to None. + device(PlaceLike|None, optional): The desired device of returned tensor. + if None, uses the current device for the default tensor type (see paddle.device.set_device()). + device will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types. Default: None. + pin_memory(bool, optional): If set, return tensor would be allocated in the pinned memory. Works only for CPU tensors. Default: False + requires_grad(bool, optional): If autograd should record operations on the returned tensor. Default: False. + + Returns: + Tensor: A 1-D tensor of shape `(window_length,)` containing the Hamming window. + + Examples: + .. code-block:: python + + >>> import paddle + + >>> win = paddle.hamming_window(400, requires_grad=True) + >>> win = paddle.hamming_window(256, alpha=0.5, beta=0.5) + """ + w0 = get_window('hamming', window_length, fftbins=periodic, dtype=dtype) + alpha0, beta0 = 0.54, 0.46 + B = beta / beta0 + A = alpha - B * alpha0 + w = A + B * w0 + return _apply_window_postprocess( + w, + layout=layout, + device=device, + pin_memory=pin_memory, + requires_grad=requires_grad, + ) + + +def hann_window( + window_length: int, + periodic: bool = True, + *, + dtype: str = 'float64', + layout: str | None = None, + device: PlaceLike | None = None, + pin_memory: bool = False, + requires_grad: bool = False, +): + """ + Compute a Hann window. + + Args: + window_length (int): The size of the returned window. Must be positive. + periodic (bool, optional): If True, returns a window for use as a periodic function; if False, returns a symmetric window. Defaults to True. + dtype (str, optional): The data type of the returned tensor. Defaults to 'float64'. + layout (str, optional): Only included for API consistency with PyTorch; ignored in Paddle. Defaults to None. + device(PlaceLike|None, optional): The desired device of returned tensor. + if None, uses the current device for the default tensor type (see paddle.device.set_device()). + device will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types. Default: None. + pin_memory(bool, optional): If set, return tensor would be allocated in the pinned memory. Works only for CPU tensors. Default: False + requires_grad(bool, optional): If autograd should record operations on the returned tensor. Default: False. + + Returns: + Tensor: A 1-D tensor of shape `(window_length,)` containing the Hann window. + + Examples: + .. code-block:: python + + >>> import paddle + + >>> win = paddle.hann_window(512) + >>> win = paddle.hann_window(512, requires_grad=True) + """ + w = get_window('hann', window_length, fftbins=periodic, dtype=dtype) + return _apply_window_postprocess( + w, + layout=layout, + device=device, + pin_memory=pin_memory, + requires_grad=requires_grad, + ) + + +def kaiser_window( + window_length: int, + periodic: bool = True, + beta: float = 12.0, + *, + dtype: str = 'float64', + layout: str | None = None, + device: PlaceLike | None = None, + pin_memory: bool = False, + requires_grad: bool = False, +): + """ + Compute a Kaiser window. + + Args: + window_length (int): The size of the returned window. Must be positive. + periodic (bool, optional): If True, returns a window for use as a periodic function; if False, returns a symmetric window. Defaults to True. + beta (float, optional): Shape parameter for the window. Defaults to 12.0. + dtype (str, optional): The data type of the returned tensor. Defaults to 'float64'. + layout (str, optional): Only included for API consistency with PyTorch; ignored in Paddle. Defaults to None. + device(PlaceLike|None, optional): The desired device of returned tensor. + if None, uses the current device for the default tensor type (see paddle.device.set_device()). + device will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types. Default: None. + pin_memory(bool, optional): If set, return tensor would be allocated in the pinned memory. Works only for CPU tensors. Default: False + requires_grad(bool, optional): If autograd should record operations on the returned tensor. Default: False. + + Returns: + Tensor: A 1-D tensor of shape `(window_length,)` containing the Kaiser window. + + Examples: + .. code-block:: python + + >>> import paddle + + >>> win = paddle.kaiser_window(400, beta=8.6) + >>> win = paddle.kaiser_window(400, requires_grad=True) + """ + w = get_window( + ('kaiser', beta), window_length, fftbins=periodic, dtype=dtype + ) + return _apply_window_postprocess( + w, + layout=layout, + device=device, + pin_memory=pin_memory, + requires_grad=requires_grad, + ) + + +def blackman_window( + window_length: int, + periodic: bool = True, + *, + dtype: str = 'float64', + layout: str | None = None, + device: PlaceLike | None = None, + pin_memory: bool = False, + requires_grad: bool = False, +): + """ + Compute a Blackman window. + + Args: + window_length (int): The size of the returned window. Must be positive. + periodic (bool, optional): If True, returns a window for use as a periodic function; if False, returns a symmetric window. Defaults to True. + dtype (str, optional): The data type of the returned tensor. Defaults to 'float64'. + layout (str, optional): Only included for API consistency with PyTorch; ignored in Paddle. Defaults to None. + device(PlaceLike|None, optional): The desired device of returned tensor. + if None, uses the current device for the default tensor type (see paddle.device.set_device()). + device will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types. Default: None. + pin_memory(bool, optional): If set, return tensor would be allocated in the pinned memory. Works only for CPU tensors. Default: False + requires_grad(bool, optional): If autograd should record operations on the returned tensor. Default: False. + + Returns: + Tensor: A 1-D tensor of shape `(window_length,)` containing the Blackman window. + + Examples: + .. code-block:: python + + >>> import paddle + + >>> win = paddle.blackman_window(256) + >>> win = paddle.blackman_window(256, requires_grad=True) + """ + w = get_window('blackman', window_length, fftbins=periodic, dtype=dtype) + return _apply_window_postprocess( + w, + layout=layout, + device=device, + pin_memory=pin_memory, + requires_grad=requires_grad, + ) + + +def bartlett_window( + window_length: int, + periodic: bool = True, + *, + dtype: str = 'float64', + layout: str | None = None, + device: PlaceLike | None = None, + pin_memory: bool = False, + requires_grad: bool = False, +): + """ + Compute a Bartlett window. + + Args: + window_length (int): The size of the returned window. Must be positive. + periodic (bool, optional): If True, returns a window for use as a periodic function; if False, returns a symmetric window. Defaults to True. + dtype (str, optional): The data type of the returned tensor. Defaults to 'float64'. + layout (str, optional): Only included for API consistency with PyTorch; ignored in Paddle. Defaults to None. + device(PlaceLike|None, optional): The desired device of returned tensor. + if None, uses the current device for the default tensor type (see paddle.device.set_device()). + device will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types. Default: None. + pin_memory(bool, optional): If set, return tensor would be allocated in the pinned memory. Works only for CPU tensors. Default: False + requires_grad(bool, optional): If autograd should record operations on the returned tensor. Default: False. + + Returns: + Tensor: A 1-D tensor of shape `(window_length,)` containing the Bartlett window. + + Examples: + .. code-block:: python + + >>> import paddle + + >>> n_fft = 512 + >>> win = paddle.bartlett_window(n_fft) + + >>> win = paddle.bartlett_window(n_fft, requires_grad=True) + """ + w = get_window('bartlett', window_length, fftbins=periodic, dtype=dtype) + return _apply_window_postprocess( + w, + layout=layout, + device=device, + pin_memory=pin_memory, + requires_grad=requires_grad, + ) diff --git a/test/legacy_test/test_window.py b/test/legacy_test/test_window.py new file mode 100644 index 00000000000000..3d08f56adc1fc3 --- /dev/null +++ b/test/legacy_test/test_window.py @@ -0,0 +1,172 @@ +# Copyright (c) 2025 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. + +import unittest + +import paddle +from paddle.audio.functional.window import get_window + + +class TestWindowFunctions(unittest.TestCase): + def setUp(self): + paddle.set_device("cpu") + + def test_hamming_alpha_beta_transform_and_requires_grad(self): + N = 16 + w0 = get_window('hamming', N, fftbins=True, dtype='float64') + + # Custom alpha/beta, verify linear transformation A + B * w0 + alpha, beta = 0.60, 0.40 + w = paddle.hamming_window( + N, + periodic=True, + alpha=alpha, + beta=beta, + dtype='float64', + requires_grad=True, + ) + self.assertEqual(w.dtype, paddle.float64) + self.assertFalse(w.stop_gradient) + # Linear equivalence: w ≈ A + B * w0 + alpha0, beta0 = 0.54, 0.46 + B = beta / beta0 + A = alpha - B * alpha0 + self.assertTrue(paddle.allclose(w, A + B * w0, atol=1e-12)) + + def test_hamming_layout_warning(self): + N = 8 + # Pass layout != None to trigger warning branch (ignored) + w = paddle.hamming_window( + N, + periodic=False, + alpha=0.54, + beta=0.46, + dtype='float32', + layout='strided', + device='cpu', + requires_grad=False, + ) + self.assertEqual(w.dtype, paddle.float32) + self.assertTrue(w.stop_gradient) + self.assertEqual(list(w.shape), [N]) + + def test_hamming_device_gpu_pin_memory(self): + if paddle.is_compiled_with_cuda(): + N = 12 + # Explicitly set device to cuda:0 / gpu:0 should work (PlaceLike supports str) + w = paddle.hamming_window( + N, + periodic=True, + alpha=0.54, + beta=0.46, + dtype='float32', + layout=None, + device='gpu:0', + pin_memory=True, + requires_grad=None, + ) + self.assertEqual(list(w.shape), [N]) + self.assertIn('gpu', str(w.place)) + + def test_hann_basic_paths(self): + N = 10 + # Pass layout=None; set requires_grad=True + w = paddle.hann_window( + N, + periodic=True, + dtype='float64', + layout=None, + device='cpu', + requires_grad=True, + ) + self.assertEqual(list(w.shape), [N]) + self.assertFalse(w.stop_gradient) + + # Test layout != None + w2 = paddle.hann_window( + N, + periodic=False, + dtype='float32', + layout='strided', + device='cpu', + requires_grad=False, + ) + self.assertEqual(w2.dtype, paddle.float32) + self.assertTrue(w2.stop_gradient) + + def test_blackman_and_bartlett_basic(self): + N = 9 + wb = paddle.blackman_window( + N, + periodic=True, + dtype='float64', + layout=None, + device=None, + requires_grad=None, + ) + self.assertEqual(list(wb.shape), [N]) + + wl = paddle.bartlett_window( + N, + periodic=False, + dtype='float32', + layout='strided', + device='cpu', + requires_grad=True, + ) + self.assertEqual(list(wl.shape), [N]) + self.assertFalse(wl.stop_gradient) + + def test_kaiser_beta_and_paths(self): + N = 7 + beta = 6.0 + w = paddle.kaiser_window( + N, + periodic=True, + beta=beta, + dtype='float64', + layout=None, + device=None, + requires_grad=None, + ) + self.assertEqual(list(w.shape), [N]) + + # Test layout != None + requires_grad + w2 = paddle.kaiser_window( + N, + periodic=False, + beta=8.0, + dtype='float32', + layout='strided', + device='cpu', + requires_grad=False, + ) + self.assertEqual(w2.dtype, paddle.float32) + self.assertTrue(w2.stop_gradient) + + def test_hamming_periodic_vs_symmetric(self): + # Test periodic True/False length handling (DFT symmetry/periodic) + N = 11 + w_per = paddle.hamming_window( + N, periodic=True, alpha=0.54, beta=0.46, dtype='float64' + ) + w_sym = paddle.hamming_window( + N, periodic=False, alpha=0.54, beta=0.46, dtype='float64' + ) + self.assertEqual(list(w_per.shape), [N]) + self.assertEqual(list(w_sym.shape), [N]) + + +if __name__ == '__main__': + unittest.main()