# Copyright (c) 2021 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. """Pseudo QMF modules.""" import numpy as np import paddle import paddle.nn.functional as F from scipy.signal import kaiser def design_prototype_filter(taps=62, cutoff_ratio=0.142, beta=9.0): """Design prototype filter for PQMF. This method is based on `A Kaiser window approach for the design of prototype filters of cosine modulated filterbanks`_. Parameters ---------- taps : int The number of filter taps. cutoff_ratio : float Cut-off frequency ratio. beta : float Beta coefficient for kaiser window. Returns ---------- ndarray Impluse response of prototype filter (taps + 1,). .. _`A Kaiser window approach for the design of prototype filters of cosine modulated filterbanks`: https://ieeexplore.ieee.org/abstract/document/681427 """ # check the arguments are valid assert taps % 2 == 0, "The number of taps mush be even number." assert 0.0 < cutoff_ratio < 1.0, "Cutoff ratio must be > 0.0 and < 1.0." # make initial filter omega_c = np.pi * cutoff_ratio with np.errstate(invalid="ignore"): h_i = np.sin(omega_c * (np.arange(taps + 1) - 0.5 * taps)) / ( np.pi * (np.arange(taps + 1) - 0.5 * taps)) h_i[taps // 2] = np.cos(0) * cutoff_ratio # fix nan due to indeterminate form # apply kaiser window w = kaiser(taps + 1, beta) h = h_i * w return h class PQMF(paddle.nn.Layer): """PQMF module. This module is based on `Near-perfect-reconstruction pseudo-QMF banks`_. .. _`Near-perfect-reconstruction pseudo-QMF banks`: https://ieeexplore.ieee.org/document/258122 """ def __init__(self, subbands=4, taps=62, cutoff_ratio=0.142, beta=9.0): """Initilize PQMF module. The cutoff_ratio and beta parameters are optimized for #subbands = 4. See dicussion in https://github.com/kan-bayashi/ParallelWaveGAN/issues/195. Parameters ---------- subbands : int The number of subbands. taps : int The number of filter taps. cutoff_ratio : float Cut-off frequency ratio. beta : float Beta coefficient for kaiser window. """ super().__init__() h_proto = design_prototype_filter(taps, cutoff_ratio, beta) h_analysis = np.zeros((subbands, len(h_proto))) h_synthesis = np.zeros((subbands, len(h_proto))) for k in range(subbands): h_analysis[k] = ( 2 * h_proto * np.cos((2 * k + 1) * (np.pi / (2 * subbands)) * ( np.arange(taps + 1) - (taps / 2)) + (-1)**k * np.pi / 4)) h_synthesis[k] = ( 2 * h_proto * np.cos((2 * k + 1) * (np.pi / (2 * subbands)) * ( np.arange(taps + 1) - (taps / 2)) - (-1)**k * np.pi / 4)) # convert to tensor self.analysis_filter = paddle.to_tensor( h_analysis, dtype="float32").unsqueeze(1) self.synthesis_filter = paddle.to_tensor( h_synthesis, dtype="float32").unsqueeze(0) # filter for downsampling & upsampling updown_filter = paddle.zeros( (subbands, subbands, subbands), dtype="float32") for k in range(subbands): updown_filter[k, k, 0] = 1.0 self.updown_filter = updown_filter self.subbands = subbands # keep padding info self.pad_fn = paddle.nn.Pad1D(taps // 2, mode='constant', value=0.0) def analysis(self, x): """Analysis with PQMF. Parameters ---------- x : Tensor Input tensor (B, 1, T). Returns ---------- Tensor Output tensor (B, subbands, T // subbands). """ x = F.conv1d(self.pad_fn(x), self.analysis_filter) return F.conv1d(x, self.updown_filter, stride=self.subbands) def synthesis(self, x): """Synthesis with PQMF. Parameters ---------- x : Tensor Input tensor (B, subbands, T // subbands). Returns ---------- Tensor Output tensor (B, 1, T). """ x = F.conv1d_transpose( x, self.updown_filter * self.subbands, stride=self.subbands) return F.conv1d(self.pad_fn(x), self.synthesis_filter) # when converting dygraph to static graph, can not use self.pqmf.synthesis directly def forward(self, x): return self.synthesis(x)