window.py 11.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
# Copyright (c) 2022 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
import math
14
from typing import List, Tuple, Union
15 16 17 18 19

import paddle
from paddle import Tensor


20
class WindowFunctionRegister:
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
    def __init__(self):
        self._functions_dict = dict()

    def register(self, func=None):
        def add_subfunction(func):
            name = func.__name__
            self._functions_dict[name] = func
            return func

        return add_subfunction

    def get(self, name):
        return self._functions_dict[name]


window_function_register = WindowFunctionRegister()


@window_function_register.register()
40 41 42 43 44
def _cat(x: List[Tensor], data_type: str) -> Tensor:
    l = [paddle.to_tensor(_, data_type) for _ in x]
    return paddle.concat(l)


45
@window_function_register.register()
46 47 48 49 50 51
def _acosh(x: Union[Tensor, float]) -> Tensor:
    if isinstance(x, float):
        return math.log(x + math.sqrt(x**2 - 1))
    return paddle.log(x + paddle.sqrt(paddle.square(x) - 1))


52
@window_function_register.register()
53
def _extend(M: int, sym: bool) -> bool:
54
    """Extend window by 1 sample if needed for DFT-even symmetry."""
55 56 57 58 59 60
    if not sym:
        return M + 1, True
    else:
        return M, False


61
@window_function_register.register()
62
def _len_guards(M: int) -> bool:
63
    """Handle small or incorrect window lengths."""
64 65 66 67 68 69
    if int(M) != M or M < 0:
        raise ValueError('Window length M must be a non-negative integer')

    return M <= 1


70
@window_function_register.register()
71
def _truncate(w: Tensor, needed: bool) -> Tensor:
72
    """Truncate window by 1 sample if needed for DFT-even symmetry."""
73 74 75 76 77 78
    if needed:
        return w[:-1]
    else:
        return w


79
@window_function_register.register()
80 81 82
def _general_gaussian(
    M: int, p, sig, sym: bool = True, dtype: str = 'float64'
) -> Tensor:
83 84 85 86
    """Compute a window with a generalized Gaussian shape.
    This function is consistent with scipy.signal.windows.general_gaussian().
    """
    if _len_guards(M):
87
        return paddle.ones((M,), dtype=dtype)
88 89 90
    M, needs_trunc = _extend(M, sym)

    n = paddle.arange(0, M, dtype=dtype) - (M - 1.0) / 2.0
91
    w = paddle.exp(-0.5 * paddle.abs(n / sig) ** (2 * p))
92 93 94 95

    return _truncate(w, needs_trunc)


96
@window_function_register.register()
97 98 99
def _general_cosine(
    M: int, a: float, sym: bool = True, dtype: str = 'float64'
) -> Tensor:
100 101 102 103
    """Compute a generic weighted sum of cosine terms window.
    This function is consistent with scipy.signal.windows.general_cosine().
    """
    if _len_guards(M):
104
        return paddle.ones((M,), dtype=dtype)
105 106
    M, needs_trunc = _extend(M, sym)
    fac = paddle.linspace(-math.pi, math.pi, M, dtype=dtype)
107
    w = paddle.zeros((M,), dtype=dtype)
108 109 110 111 112
    for k in range(len(a)):
        w += a[k] * paddle.cos(k * fac)
    return _truncate(w, needs_trunc)


113
@window_function_register.register()
114 115 116
def _general_hamming(
    M: int, alpha: float, sym: bool = True, dtype: str = 'float64'
) -> Tensor:
117 118 119
    """Compute a generalized Hamming window.
    This function is consistent with scipy.signal.windows.general_hamming()
    """
120
    return _general_cosine(M, [alpha, 1.0 - alpha], sym, dtype=dtype)
121 122


123
@window_function_register.register()
124 125 126
def _taylor(
    M: int, nbar=4, sll=30, norm=True, sym: bool = True, dtype: str = 'float64'
) -> Tensor:
127 128 129 130 131
    """Compute a Taylor window.
    The Taylor window taper function approximates the Dolph-Chebyshev window's
    constant sidelobe level for a parameterized number of near-in sidelobes.
    """
    if _len_guards(M):
132
        return paddle.ones((M,), dtype=dtype)
133 134 135 136
    M, needs_trunc = _extend(M, sym)
    # Original text uses a negative sidelobe level parameter and then negates
    # it in the calculation of B. To keep consistent with other methods we
    # assume the sidelobe level parameter to be positive.
137
    B = 10 ** (sll / 20)
138
    A = _acosh(B) / math.pi
139
    s2 = nbar**2 / (A**2 + (nbar - 0.5) ** 2)
140 141
    ma = paddle.arange(1, nbar, dtype=dtype)

142
    Fm = paddle.empty((nbar - 1,), dtype=dtype)
143 144 145 146 147
    signs = paddle.empty_like(ma)
    signs[::2] = 1
    signs[1::2] = -1
    m2 = ma * ma
    for mi in range(len(ma)):
148 149 150
        numer = signs[mi] * paddle.prod(
            1 - m2[mi] / s2 / (A**2 + (ma - 0.5) ** 2)
        )
151
        if mi == 0:
152
            denom = 2 * paddle.prod(1 - m2[mi] / m2[mi + 1 :])
153 154 155
        elif mi == len(ma) - 1:
            denom = 2 * paddle.prod(1 - m2[mi] / m2[:mi])
        else:
156 157 158 159 160
            denom = (
                2
                * paddle.prod(1 - m2[mi] / m2[:mi])
                * paddle.prod(1 - m2[mi] / m2[mi + 1 :])
            )
161 162 163 164 165 166

        Fm[mi] = numer / denom

    def W(n):
        return 1 + 2 * paddle.matmul(
            Fm.unsqueeze(0),
167 168
            paddle.cos(2 * math.pi * ma.unsqueeze(1) * (n - M / 2.0 + 0.5) / M),
        )
169 170 171 172 173 174 175 176 177 178 179

    w = W(paddle.arange(0, M, dtype=dtype))

    # normalize (Note that this is not described in the original text [1])
    if norm:
        scale = 1.0 / W((M - 1) / 2)
        w *= scale
    w = w.squeeze()
    return _truncate(w, needs_trunc)


180
@window_function_register.register()
181 182 183 184 185 186 187 188
def _hamming(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor:
    """Compute a Hamming window.
    The Hamming window is a taper formed by using a raised cosine with
    non-zero endpoints, optimized to minimize the nearest side lobe.
    """
    return _general_hamming(M, 0.54, sym, dtype=dtype)


189
@window_function_register.register()
190 191 192 193 194 195 196 197
def _hann(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor:
    """Compute a Hann window.
    The Hann window is a taper formed by using a raised cosine or sine-squared
    with ends that touch zero.
    """
    return _general_hamming(M, 0.5, sym, dtype=dtype)


198
@window_function_register.register()
199 200 201
def _tukey(
    M: int, alpha=0.5, sym: bool = True, dtype: str = 'float64'
) -> Tensor:
202 203 204 205
    """Compute a Tukey window.
    The Tukey window is also known as a tapered cosine window.
    """
    if _len_guards(M):
206
        return paddle.ones((M,), dtype=dtype)
207 208

    if alpha <= 0:
209
        return paddle.ones((M,), dtype=dtype)
210
    elif alpha >= 1.0:
211
        return _hann(M, sym=sym)
212 213 214 215 216

    M, needs_trunc = _extend(M, sym)

    n = paddle.arange(0, M, dtype=dtype)
    width = int(alpha * (M - 1) / 2.0)
217 218 219
    n1 = n[0 : width + 1]
    n2 = n[width + 1 : M - width - 1]
    n3 = n[M - width - 1 :]
220 221 222

    w1 = 0.5 * (1 + paddle.cos(math.pi * (-1 + 2.0 * n1 / alpha / (M - 1))))
    w2 = paddle.ones(n2.shape, dtype=dtype)
223 224 225 226
    w3 = 0.5 * (
        1
        + paddle.cos(math.pi * (-2.0 / alpha + 1 + 2.0 * n3 / alpha / (M - 1)))
    )
227 228 229 230 231
    w = paddle.concat([w1, w2, w3])

    return _truncate(w, needs_trunc)


232
@window_function_register.register()
233 234 235
def _gaussian(
    M: int, std: float, sym: bool = True, dtype: str = 'float64'
) -> Tensor:
236 237 238 239
    """Compute a Gaussian window.
    The Gaussian widows has a Gaussian shape defined by the standard deviation(std).
    """
    if _len_guards(M):
240
        return paddle.ones((M,), dtype=dtype)
241 242 243 244
    M, needs_trunc = _extend(M, sym)

    n = paddle.arange(0, M, dtype=dtype) - (M - 1.0) / 2.0
    sig2 = 2 * std * std
245
    w = paddle.exp(-(n**2) / sig2)
246 247 248 249

    return _truncate(w, needs_trunc)


250
@window_function_register.register()
251 252 253 254
def _exponential(
    M: int, center=None, tau=1.0, sym: bool = True, dtype: str = 'float64'
) -> Tensor:
    """Compute an exponential (or Poisson) window."""
255 256 257
    if sym and center is not None:
        raise ValueError("If sym==True, center must be None.")
    if _len_guards(M):
258
        return paddle.ones((M,), dtype=dtype)
259 260 261 262 263 264 265 266 267 268 269
    M, needs_trunc = _extend(M, sym)

    if center is None:
        center = (M - 1) / 2

    n = paddle.arange(0, M, dtype=dtype)
    w = paddle.exp(-paddle.abs(n - center) / tau)

    return _truncate(w, needs_trunc)


270
@window_function_register.register()
271
def _triang(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor:
272
    """Compute a triangular window."""
273
    if _len_guards(M):
274
        return paddle.ones((M,), dtype=dtype)
275 276 277 278 279 280 281 282 283 284 285 286 287
    M, needs_trunc = _extend(M, sym)

    n = paddle.arange(1, (M + 1) // 2 + 1, dtype=dtype)
    if M % 2 == 0:
        w = (2 * n - 1.0) / M
        w = paddle.concat([w, w[::-1]])
    else:
        w = 2 * n / (M + 1.0)
        w = paddle.concat([w, w[-2::-1]])

    return _truncate(w, needs_trunc)


288
@window_function_register.register()
289 290 291 292 293
def _bohman(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor:
    """Compute a Bohman window.
    The Bohman window is the autocorrelation of a cosine window.
    """
    if _len_guards(M):
294
        return paddle.ones((M,), dtype=dtype)
295 296 297 298
    M, needs_trunc = _extend(M, sym)

    fac = paddle.abs(paddle.linspace(-1, 1, M, dtype=dtype)[1:-1])
    w = (1 - fac) * paddle.cos(math.pi * fac) + 1.0 / math.pi * paddle.sin(
299 300
        math.pi * fac
    )
301 302 303 304 305
    w = _cat([0, w, 0], dtype)

    return _truncate(w, needs_trunc)


306
@window_function_register.register()
307 308 309 310 311 312 313 314 315 316
def _blackman(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor:
    """Compute a Blackman window.
    The Blackman window is a taper formed by using the first three terms of
    a summation of cosines. It was designed to have close to the minimal
    leakage possible.  It is close to optimal, only slightly worse than a
    Kaiser window.
    """
    return _general_cosine(M, [0.42, 0.50, 0.08], sym, dtype=dtype)


317
@window_function_register.register()
318
def _cosine(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor:
319
    """Compute a window with a simple cosine shape."""
320
    if _len_guards(M):
321
        return paddle.ones((M,), dtype=dtype)
322
    M, needs_trunc = _extend(M, sym)
323
    w = paddle.sin(math.pi / M * (paddle.arange(0, M, dtype=dtype) + 0.5))
324 325 326 327

    return _truncate(w, needs_trunc)


328 329 330 331 332 333
def get_window(
    window: Union[str, Tuple[str, float]],
    win_length: int,
    fftbins: bool = True,
    dtype: str = 'float64',
) -> Tensor:
334 335 336
    """Return a window of a given length and type.

    Args:
337
        window (Union[str, Tuple[str, float]]): The window function applied to the signal before the Fourier transform. Supported window functions: 'hamming', 'hann', 'gaussian', 'general_gaussian', 'exponential', 'triang', 'bohman', 'blackman', 'cosine', 'tukey', 'taylor'.
338 339 340 341 342 343
        win_length (int): Number of samples.
        fftbins (bool, optional): If True, create a "periodic" window. Otherwise, create a "symmetric" window, for use in filter design. Defaults to True.
        dtype (str, optional): The data type of the return window. Defaults to 'float64'.

    Returns:
        Tensor: The window represented as a tensor.
Y
YangZhou 已提交
344 345 346 347 348 349 350 351 352 353

    Examples:
        .. code-block:: python

            import paddle

            n_fft = 512
            cosine_window = paddle.audio.functional.get_window('cosine', n_fft)

            std = 7
354
            gaussian_window = paddle.audio.functional.get_window(('gaussian',std), n_fft)
355 356 357 358 359 360 361 362 363 364
    """
    sym = not fftbins

    args = ()
    if isinstance(window, tuple):
        winstr = window[0]
        if len(window) > 1:
            args = window[1:]
    elif isinstance(window, str):
        if window in ['gaussian', 'exponential']:
365 366 367 368
            raise ValueError(
                "The '" + window + "' window needs one or "
                "more parameters -- pass a tuple."
            )
369 370 371
        else:
            winstr = window
    else:
372 373 374
        raise ValueError(
            "%s as window type is not supported." % str(type(window))
        )
375 376

    try:
377 378
        winfunc = window_function_register.get('_' + winstr)
    except KeyError as e:
379 380
        raise ValueError("Unknown window type.") from e

381
    params = (win_length,) + args
382 383
    kwargs = {'sym': sym}
    return winfunc(*params, dtype=dtype, **kwargs)