Skip to content

Commit

Permalink
Merge pull request #1 from takenori-y/release
Browse files Browse the repository at this point in the history
Prepare release
  • Loading branch information
takenori-y authored Jan 21, 2025
2 parents 3035e23 + 261cb06 commit 3966fde
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 40 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

This is an unofficial Python reimplemantation of the [legacy-STRAIGHT](https://github.com/HidekiKawahara/legacy_STRAIGHT), which was originally written in MATLAB.

[![Stable Manual](https://img.shields.io/badge/docs-latest-blue.svg)](https://takenori-y.github.io/pylstraight/0.1.0/)
[![Stable Manual](https://img.shields.io/badge/docs-stable-blue.svg)](https://takenori-y.github.io/pylstraight/0.1.0/)
[![Downloads](https://static.pepy.tech/badge/pylstriaght)](https://pepy.tech/project/pylstriaght)
[![Python Version](https://img.shields.io/pypi/pyversions/pylstraight.svg)](https://pypi.python.org/pypi/pylstraight)
[![PyPI Version](https://img.shields.io/pypi/v/diffsptk.svg)](https://pypi.python.org/pypi/diffsptk)
[![PyPI Version](https://img.shields.io/pypi/v/pylstraight.svg)](https://pypi.python.org/pypi/pylstraight)
[![Codecov](https://codecov.io/gh/takenori-y/pylstraight/branch/master/graph/badge.svg)](https://app.codecov.io/gh/takenori-y/pylstraight)
[![License](https://img.shields.io/github/license/takenori-y/pylstraight.svg)](https://github.com/takenori-y/pylstraight/blob/master/LICENSE)
[![GitHub Actions](https://github.com/takenori-y/pylstraight/workflows/package/badge.svg)](https://github.com/takenori-y/pylstraight/actions)
Expand Down
7 changes: 7 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
coverage:
status:
project:
default:
target: auto
threshold: 1%
patch: off
56 changes: 28 additions & 28 deletions pylstraight/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ def f0_to_f0(
f0 : np.ndarray [shape=(nframe,)]
The input F0.
in_format : ['pitch', 'f0', 'logf0']
in_format : ['inverse', 'linear', 'log']
The format of the input F0.
out_format : ['pitch', 'f0', 'logf0']
out_format : ['inverse', 'linear', 'log']
The format of the output F0.
fs : int
Expand All @@ -63,9 +63,9 @@ def f0_to_f0(
>>> import pylstraight as pyls
>>> import numpy as np
>>> f0 = np.array([100, 200, 0, 400], dtype=float)
>>> pyls.f0_to_f0(f0, "f0", "logf0")
>>> pyls.f0_to_f0(f0, "linear", "log")
array([ 4.60517019e+00, 5.29831737e+00, -1.00000000e+10, 5.99146455e+00])
>>> pyls.f0_to_f0(f0, "f0", "pitch", fs=16000)
>>> pyls.f0_to_f0(f0, "linear", "inverse", fs=16000)
array([160., 80., 0., 40.])
"""
Expand All @@ -76,28 +76,28 @@ def f0_to_f0(
if in_format == out_format:
return f0

if (in_format == "pitch" or out_format == "pitch") and fs <= 0:
if (in_format == "inverse" or out_format == "inverse") and fs <= 0:
msg = "Sampling frequency is required."
raise ValueError(msg)

voiced = f0 != (-1e10 if in_format == "logf0" else 0)
voiced = f0 != (-1e10 if in_format == "log" else 0)
voiced_func = {
"pitch": {
"f0": lambda x: fs / x,
"logf0": lambda x: np.log(fs / x),
"inverse": {
"linear": lambda x: fs / x,
"log": lambda x: np.log(fs / x),
},
"f0": {
"pitch": lambda x: fs / x,
"logf0": np.log,
"linear": {
"inverse": lambda x: fs / x,
"log": np.log,
},
"logf0": {
"pitch": lambda x: fs / np.exp(x),
"f0": np.exp,
"log": {
"inverse": lambda x: fs / np.exp(x),
"linear": np.exp,
},
}

unvoiced = ~voiced
unvoiced_value = -1e10 if out_format == "logf0" else 0
unvoiced_value = -1e10 if out_format == "log" else 0

new_f0 = np.empty_like(f0)
try:
Expand Down Expand Up @@ -299,7 +299,7 @@ def extract_f0(
*,
frame_shift: float = 5.0,
f0_range: tuple[float, float] = (40.0, 400.0),
f0_format: str = "f0",
f0_format: str = "linear",
f0_param: F0Param | None = None,
return_aux: bool = False,
) -> np.ndarray:
Expand All @@ -319,7 +319,7 @@ def extract_f0(
f0_range : tuple[float, float]
The lower and upper bounds of F0 search in Hz.
f0_format : ['pitch', 'f0', 'logf0']
f0_format : ['inverse', 'linear', 'log']
The output format.
f0_param : F0Param or None
Expand Down Expand Up @@ -382,7 +382,7 @@ def extract_f0(
f0, vuv, auxouts = MulticueF0v14(x, fs, f0_param)
f0 *= vuv
f0[f0_ceil < f0] = f0_ceil
f0 = f0_to_f0(f0, "f0", f0_format, fs=fs)
f0 = f0_to_f0(f0, "linear", f0_format, fs=fs)

if return_aux:
return f0, auxouts
Expand All @@ -398,7 +398,7 @@ def extract_ap(
*,
frame_shift: float = 5.0,
ap_floor: float = 0.001,
f0_format: str = "f0",
f0_format: str = "linear",
ap_format: str = "a",
ap_param: ApParam | None = None,
) -> np.ndarray:
Expand All @@ -424,7 +424,7 @@ def extract_ap(
ap_floor : float
The minimum value of aperiodicity.
f0_format : ['pitch', 'f0', 'logf0']
f0_format : ['inverse', 'linear', 'log']
The input format of the F0.
ap_format : ['a', 'p', 'a/p', 'p/a']
Expand Down Expand Up @@ -485,7 +485,7 @@ def extract_ap(
ap = exstraightAPind(
x,
fs,
f0_to_f0(f0, f0_format, "f0", fs=fs),
f0_to_f0(f0, f0_format, "linear", fs=fs),
None if aux is None else aux.refined_cn,
ap_param,
)
Expand All @@ -500,7 +500,7 @@ def extract_sp(
f0: np.ndarray,
*,
frame_shift: float = 5.0,
f0_format: str = "f0",
f0_format: str = "linear",
sp_format: str = "linear",
sp_param: SpParam | None = None,
) -> np.ndarray:
Expand All @@ -520,7 +520,7 @@ def extract_sp(
frame_shift : float
The frame shift in msec.
f0_format : ['pitch', 'f0', 'logf0']
f0_format : ['inverse', 'linear', 'log']
The input format of the F0.
sp_format : ['db', 'log', 'linear', 'power']
Expand Down Expand Up @@ -577,7 +577,7 @@ def extract_sp(
)
raise ValueError(msg)

sp = exstraightspec(x, f0_to_f0(f0, f0_format, "f0", fs=fs), fs, sp_param)
sp = exstraightspec(x, f0_to_f0(f0, f0_format, "linear", fs=fs), fs, sp_param)
if scaler != 1:
sp *= scaler
return sp_to_sp(sp, "linear", sp_format)
Expand All @@ -590,7 +590,7 @@ def synthesize(
fs: int,
*,
frame_shift: float = 5.0,
f0_format: str = "f0",
f0_format: str = "linear",
ap_format: str = "a",
sp_format: str = "linear",
syn_param: SynParam | None = None,
Expand All @@ -614,7 +614,7 @@ def synthesize(
frame_shift : float
The frame shift in msec.
f0_format : ['pitch', 'f0', 'logf0']
f0_format : ['inverse', 'linear', 'log']
The format of F0.
ap_format : ['a', 'p', 'a/p', 'p/a']
Expand Down Expand Up @@ -670,7 +670,7 @@ def synthesize(
syn_param.spectral_update_interval = frame_shift

return exstraightsynth(
f0_to_f0(f0, f0_format, "f0", fs=fs),
f0_to_f0(f0, f0_format, "linear", fs=fs),
sp_to_sp(sp, sp_format, "linear"),
sp_to_sp(ap_to_ap(ap, ap_format, "a"), "linear", "db"),
fs,
Expand Down
3 changes: 2 additions & 1 deletion pylstraight/core/f0.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ def zwvlt2ifq(pm: np.ndarray, fs: float) -> np.ndarray:
npm = pm / (np.abs(pm) + 1e-10)
pif = np.abs(np.diff(npm, axis=0))
pif = np.pad(pif, ((1, 0), (0, 0)), mode="edge")
return fs / np.pi * np.asin(pif / 2)
return fs / np.pi * np.arcsin(pif / 2)


def zifq2gpm2(
Expand Down Expand Up @@ -798,6 +798,7 @@ def zremoveACinduction(
h60 = np.sum(np.abs(f - 60) < 5) / np.sum(0 < f)
if h50 < 0.2 and h60 < 0.2:
return x, ind, 0
ind = 1
fq = 50 if h60 < h50 else 60
tx = (np.arange(len(x)) + 1) / fs
fqv = mrange(-0.3, 0.025, 0.3) + fq
Expand Down
3 changes: 1 addition & 2 deletions pylstraight/core/syn.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,13 @@ def straightSynthTB07ca(
inx = np.arange(nii)
inxq = np.arange(niiNew) * ((nii - 1) / (niiNew - 1))
n2sgram = interp1(inx, n2sgram, inxq)
ap = interp1(inx, ap, inxq)
fftl = fftLengthForLowestF0
nii = niiNew

if ap.shape[1] != nii:
inx = np.arange(ap.shape[1])
inxq = np.arange(nii) * ((ap.shape[1] - 1) / (nii - 1))
ap = interp1(inx, ap, inxq, method="*linear")
ap = interp1(inx, ap, inxq)

aprms = 10 ** (ap / 20)
aprm = np.clip(aprms * 1.6 - 0.015, 0.001, 1)
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,11 @@ exclude = ["__init__.py"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"pytest.fail",
"except",
"msg",
"raise",
"if TYPE_CHECKING:",
]

[tool.pytest.ini_options]
Expand Down
38 changes: 38 additions & 0 deletions tests/test_ap.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,41 @@ def test_long_f0_input() -> None:
f0 = np.zeros(201)
ap = pyls.extract_ap(x, fs, f0)
assert len(ap) == 201


def test_conversion() -> None:
"""Test conversion between different aperiodicity formats."""

def check_reversibility(x: np.ndarray, in_format: str, out_format: str) -> bool:
"""Check if the conversion is identity.
Parameters
----------
x : np.ndarray
The input.
in_format : str
The input format.
out_format : str
The output format.
Returns
-------
out : bool
True if the conversion is identity.
"""
y = pyls.ap_to_ap(x, in_format, out_format)
z = pyls.ap_to_ap(y, out_format, in_format)
return np.allclose(x, z)

a = np.array([0.001, 0.5, 0.999])
assert check_reversibility(a, "a", "p")
assert check_reversibility(a, "a", "a/p")
assert check_reversibility(a, "a", "p/a")
p = pyls.ap_to_ap(a, "a", "p")
assert check_reversibility(p, "p", "a/p")
assert check_reversibility(p, "p", "p/a")
a_p = pyls.ap_to_ap(p, "p", "a/p")
assert check_reversibility(a_p, "a/p", "p/a")
47 changes: 47 additions & 0 deletions tests/test_f0.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ def test_very_long_frame_shift() -> None:
assert len(f0) == 1


def test_ac_induction_removal(sample_data: tuple[np.ndarray, int]) -> None:
"""Test removal of AC induction."""
x, fs = sample_data
t = np.arange(len(x)) / fs
ac_frequency = 50
ac_amplitude = 1e-3
ac_noise = ac_amplitude * np.sin(2 * np.pi * ac_frequency * t)
f0 = pyls.extract_f0(x, fs)
f02 = pyls.extract_f0(x + ac_noise, fs)
assert len(f0[0 < f0]) == len(f02[0 < f02])


def test_if_number_of_harmonic(sample_data: tuple[np.ndarray, int]) -> None:
"""Test if_number_of_harmonic parameter."""
x, fs = sample_data
Expand All @@ -111,3 +123,38 @@ def test_if_number_of_harmonic(sample_data: tuple[np.ndarray, int]) -> None:
assert abs(len(f01[0 < f01]) - len(f03[0 < f03])) <= 1
assert np.max(np.abs(f01[voiced] - f02[voiced])) < 5
assert np.max(np.abs(f01[voiced] - f03[voiced])) < 5


def test_conversion() -> None:
"""Test conversion between different f0 formats."""
fs = 8000

def check_reversibility(x: np.ndarray, in_format: str, out_format: str) -> bool:
"""Check if the conversion is identity.
Parameters
----------
x : np.ndarray
The input.
in_format : str
The input format.
out_format : str
The output format.
Returns
-------
out : bool
True if the conversion is identity.
"""
y = pyls.f0_to_f0(x, in_format, out_format, fs)
z = pyls.f0_to_f0(y, out_format, in_format, fs)
return np.allclose(x, z)

f0 = np.array([100, 0, 200]).astype(np.float64)
assert check_reversibility(f0, "linear", "log")
assert check_reversibility(f0, "linear", "inverse")
log_f0 = pyls.f0_to_f0(f0, "linear", "log")
assert check_reversibility(log_f0, "log", "inverse")
38 changes: 38 additions & 0 deletions tests/test_sp.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,41 @@ def test_long_f0_input() -> None:
f0 = np.zeros(201)
sp = pyls.extract_sp(x, fs, f0)
assert len(sp) == 200


def test_conversion() -> None:
"""Test conversion between different aperiodicity formats."""

def check_reversibility(x: np.ndarray, in_format: str, out_format: str) -> bool:
"""Check if the conversion is identity.
Parameters
----------
x : np.ndarray
The input.
in_format : str
The input format.
out_format : str
The output format.
Returns
-------
out : bool
True if the conversion is identity.
"""
y = pyls.sp_to_sp(x, in_format, out_format)
z = pyls.sp_to_sp(y, out_format, in_format)
return np.allclose(x, z)

sp = np.array([-60, 0, 60]).astype(np.float64)
assert check_reversibility(sp, "db", "log")
assert check_reversibility(sp, "db", "linear")
assert check_reversibility(sp, "db", "power")
sp = pyls.sp_to_sp(sp, "db", "log")
assert check_reversibility(sp, "log", "linear")
assert check_reversibility(sp, "log", "power")
sp = pyls.sp_to_sp(sp, "log", "linear")
assert check_reversibility(sp, "linear", "power")
Loading

0 comments on commit 3966fde

Please sign in to comment.