Source code for pysptk.util

"""
Utilities
=========

Audio files
-----------

.. autosummary::
    :toctree: generated/

    example_audio_file


Mel-cepstrum analysis
---------------------

.. autosummary::
    :toctree: generated/

    mcepalpha
"""

# I originally tried with functools.wraps to create decoraters, but it didn't
# work to me if I use multiple decoratores to decorate a function.
# Specifically, I cannot inspect argspec with a decorated function, so cannot
# get a argment name simply from it. As suggested in the following
# stackoverflow thread, using decorator package.
# https://stackoverflow.com/questions/12558505/preserve-argspec-when-decorating
from inspect import getfullargspec

import numpy as np
import pkg_resources
from decorator import decorator

# 16kHz, 16bit example audio from cmu_us_awb_arctic
# see COPYING for the license of the audio file.
EXAMPLE_AUDIO = "example_audio_data/arctic_a0007.wav"


@decorator
def apply_along_last_axis(func, *args, **kwargs):
    """Apply function along last axis

    This is used for extending vector-to-vector operations to matrix-to-matrix
    operations. This basically does the following thing in a convenient way:

    ```py
    np.apply_along_axis(func, input_vector, -1, *args, **kwargs)
    ```

    Note: The decorator assumes that the first argment of the function is the
    input vector (1d numpy array).
    """

    # Get first arg
    first_arg_name = getfullargspec(func)[0][0]
    has_positional_arg = len(args) > 0
    input_arg = args[0] if has_positional_arg else kwargs[first_arg_name]

    if input_arg.ndim == 1:
        ret = func(*args, **kwargs)
    else:
        # we need at least 1 positonal argment
        if len(args) == 0:
            args = kwargs.pop(first_arg_name)
        ret = np.apply_along_axis(func, -1, *args, **kwargs)

    return ret


@decorator
def automatic_type_conversion(func, *args, **kwargs):
    first_arg_name = getfullargspec(func)[0][0]
    has_positional_arg = len(args) > 0
    input_arg = args[0] if has_positional_arg else kwargs[first_arg_name]
    dtypein = input_arg.dtype

    # Since C functions can only accept double
    if dtypein != np.float64:
        if has_positional_arg:
            args = tuple(
                map(
                    lambda v: input_arg.astype(np.float64) if v[0] == 0 else v[1],
                    enumerate(args),
                )
            )
        else:
            kwargs[first_arg_name] = input_arg.astype(np.float64)
    return func(*args, **kwargs).astype(dtypein)


@decorator
def automatic_type_conversion_float32(func, *args, **kwargs):
    first_arg_name = getfullargspec(func)[0][0]
    has_positional_arg = len(args) > 0
    input_arg = args[0] if has_positional_arg else kwargs[first_arg_name]
    dtypein = input_arg.dtype

    if dtypein != np.float32:
        if has_positional_arg:
            args = tuple(
                map(
                    lambda v: input_arg.astype(np.float32) if v[0] == 0 else v[1],
                    enumerate(args),
                )
            )
        else:
            kwargs[first_arg_name] = input_arg.astype(np.float32)
    return func(*args, **kwargs).astype(dtypein)


def assert_gamma(gamma):
    if not (-1 <= gamma <= 0.0):
        raise ValueError("unsupported gamma: must be -1 <= gamma <= 0")


def assert_pade(pade):
    valid = [4, 5, 6, 7]
    if pade not in valid:
        raise ValueError("4, 5, 6 or 7 pade approximation is supported")


def assert_stage(stage):
    if stage < 1:
        raise ValueError("stage >= 1 (-1 <= gamma < 0)")


def ispow2(num):
    return ((num & (num - 1)) == 0) and num != 0


def assert_fftlen(fftlen):
    if not ispow2(fftlen):
        raise ValueError("fftlen must be power of 2")


[docs]def example_audio_file(): """Get the path to an included audio example file. Examples -------- >>> from scipy.io import wavfile >>> fs, x = wavfile.read(pysptk.util.example_audio_file()) >>> import matplotlib.pyplot as plt >>> plt.plot(x, label="cmu_us_awb_arctic arctic_a0007.wav") >>> plt.xlim(0, len(x)) >>> plt.legend() """ return pkg_resources.resource_filename(__name__, EXAMPLE_AUDIO)
[docs]def mcepalpha(fs, start=0.0, stop=1.0, step=0.001, num_points=1000): """Compute appropriate frequency warping parameter given a sampling frequency It would be useful to determine alpha parameter in mel-cepstrum analysis. The code is traslated from https://bitbucket.org/happyalu/mcep_alpha_calc. Parameters ---------- fs : int Sampling frequency start : float start value that will be passed to numpy.arange. Default is 0.0. stop : float stop value that will be passed to numpy.arange. Default is 1.0. step : float step value that will be passed to numpy.arange. Default is 0.001. num_points : int Number of points used in approximating mel-scale vectors in fixed- length. Returns ------- alpha : float frequency warping paramter (offen denoted by alpha) See Also -------- pysptk.sptk.mcep pysptk.sptk.mgcep """ alpha_candidates = np.arange(start, stop, step) mel = _melscale_vector(fs, num_points) distances = [ rms_distance(mel, _warping_vector(alpha, num_points)) for alpha in alpha_candidates ] return alpha_candidates[np.argmin(distances)]
def _melscale_vector(fs, length): step = (fs / 2.0) / length melscalev = 1000.0 / np.log(2) * np.log(1 + step * np.arange(0, length) / 1000.0) return melscalev / melscalev[-1] def _warping_vector(alpha, length): step = np.pi / length omega = step * np.arange(0, length) num = (1 - alpha * alpha) * np.sin(omega) den = (1 + alpha * alpha) * np.cos(omega) - 2 * alpha warpfreq = np.arctan(num / den) warpfreq[warpfreq < 0] += np.pi return warpfreq / warpfreq[-1] def rms_distance(v1, v2): d = v1 - v2 return np.sum(np.abs(d * d)) / len(v1)