diff --git a/README.md b/README.md
index c7eed13..d5702d1 100644
--- a/README.md
+++ b/README.md
@@ -142,4 +142,4 @@ You can install dependencies these by `pip install -r requirements.txt`
## Acknowledgement
-Thank all contributors ([tats-u](https://github.com/tats-u), [wuaalb](https://github.com/wuaalb), [r9y9](https://github.com/r9y9), [rikrd](https://github.com/rikrd), [kudan2510](https://github.com/kundan2510)) for making this repo better and [sotelo](https://github.com/sotelo) whose [world.py](https://github.com/sotelo/world.py) inspired this repo.
+Thank all contributors ([tats-u](https://github.com/tats-u), [wuaalb](https://github.com/wuaalb), [r9y9](https://github.com/r9y9), [rikrd](https://github.com/rikrd), [kudan2510](https://github.com/kundan2510), [sabonerune](https://github.com/sabonerune)) for making this repo better and [sotelo](https://github.com/sotelo) whose [world.py](https://github.com/sotelo/world.py) inspired this repo.
diff --git a/appveyor.yml b/appveyor.yml
index 0fa3ee4..b71e0ab 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,3 +1,7 @@
+# Do not build a feature branch that already has an open pull request
+# (otherwise every PR update triggers two identical builds).
+skip_branch_with_pr: true
+
environment:
TWINE_USERNAME: __token__
TWINE_PASSWORD:
@@ -48,6 +52,26 @@ environment:
PYTHON_ARCH: "64"
MINICONDA: C:\Miniconda3-x64
+ # - PYTHON_VERSION: "3.11"
+ # PYTHON_ARCH: "32"
+ # MINICONDA: C:\Miniconda3
+
+ - PYTHON_VERSION: "3.12"
+ PYTHON_ARCH: "64"
+ MINICONDA: C:\Miniconda3-x64
+
+ # - PYTHON_VERSION: "3.12"
+ # PYTHON_ARCH: "32"
+ # MINICONDA: C:\Miniconda3
+
+ - PYTHON_VERSION: "3.13"
+ PYTHON_ARCH: "64"
+ MINICONDA: C:\Miniconda3-x64
+
+ # - PYTHON_VERSION: "3.13"
+ # PYTHON_ARCH: "32"
+ # MINICONDA: C:\Miniconda3
+
# The above commented lines has problem executing the `test_script`
# Python >= 3.8 (32-bit)
@@ -70,7 +94,9 @@ install:
- conda update -q conda
- conda config --add channels pypi
- conda info -a
- - "conda create -q -n test-environment python=%PYTHON_VERSION% numpy cython wheel matplotlib"
+ # matplotlib/Pillow are installed from PyPI in `test_script`: conda's win-64
+ # Pillow for py3.6/3.7 ships a `_imaging` DLL that fails to load.
+ - "conda create -q -n test-environment python=%PYTHON_VERSION% numpy cython wheel"
- activate test-environment
- python -m pip install --no-cache-dir -U pip
- ps: |
@@ -82,10 +108,12 @@ build_script:
- pip install -e .
test_script:
+ - pip install "soundfile<0.13; python_version < '3.8'" "soundfile; python_version >= '3.8'"
+ - "pip install --only-binary=:all: matplotlib"
- ps: |
- $ErrorActionPreference = "Stop"
Set-Location "$env:APPVEYOR_BUILD_FOLDER\demo"
python demo.py
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Set-Location "$env:APPVEYOR_BUILD_FOLDER"
after_test:
diff --git a/lib/World b/lib/World
index 8d79b88..d625e76 160000
--- a/lib/World
+++ b/lib/World
@@ -1 +1 @@
-Subproject commit 8d79b88b7dd92e8a132996cf74080b2d6f881b98
+Subproject commit d625e7608ca23a870018f01e7c562ac683d9847f
diff --git a/pyproject.toml b/pyproject.toml
index b8eaa23..380d95b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,5 +4,6 @@ requires = [
"numpy>=2; python_version>='3.9'",
"oldest-supported-numpy; python_version<'3.9'",
"cython",
+ "cython<3.1.0; python_version<'3.9'",
]
build-backend = "setuptools.build_meta"
diff --git a/pyworld/__init__.py b/pyworld/__init__.py
index 73ba9e3..5e061cd 100644
--- a/pyworld/__init__.py
+++ b/pyworld/__init__.py
@@ -10,8 +10,16 @@
from __future__ import division, print_function, absolute_import
-import pkg_resources
+import sys
-__version__ = pkg_resources.get_distribution('pyworld').version
+if sys.version_info >= (3, 8):
+ from importlib.metadata import version, PackageNotFoundError
+else:
+ from importlib_metadata import version, PackageNotFoundError
+
+try:
+ __version__ = version('pyworld')
+except PackageNotFoundError:
+ __version__ = '0.3.6'
from .pyworld import *
diff --git a/pyworld/py.typed b/pyworld/py.typed
new file mode 100644
index 0000000..17af580
--- /dev/null
+++ b/pyworld/py.typed
@@ -0,0 +1 @@
+# PEP 561
diff --git a/pyworld/pyworld.pyi b/pyworld/pyworld.pyi
new file mode 100644
index 0000000..49e9069
--- /dev/null
+++ b/pyworld/pyworld.pyi
@@ -0,0 +1,396 @@
+from typing import Optional, Tuple
+
+import numpy as np
+
+default_frame_period: float
+default_f0_floor: float
+default_f0_ceil: float
+
+def dio(
+ x: np.ndarray,
+ fs: int,
+ f0_floor: float = ...,
+ f0_ceil: float = ...,
+ channels_in_octave: float = ...,
+ frame_period: float = ...,
+ speed: int = ...,
+ allowed_range: float = ...,
+) -> Tuple[np.ndarray, np.ndarray]:
+ """DIO F0 extraction algorithm.
+
+ Parameters
+ ----------
+ x : ndarray
+ Input waveform signal.
+ fs : int
+ Sample rate of input signal in Hz.
+ f0_floor : float
+ Lower F0 limit in Hz.
+ Default: 71.0
+ f0_ceil : float
+ Upper F0 limit in Hz.
+ Default: 800.0
+ channels_in_octave : float
+ Resolution of multiband processing; normally shouldn't be changed.
+ Default: 2.0
+ frame_period : float
+ Period between consecutive frames in milliseconds.
+ Default: 5.0
+ speed : int
+ The F0 estimator may downsample the input signal using this integer factor
+ (range [1;12]). The algorithm will then operate on a signal at fs/speed Hz
+ to reduce computational complexity, but high values may negatively impact
+ accuracy.
+ Default: 1 (no downsampling)
+ allowed_range : float
+ Threshold for voiced/unvoiced decision. Can be any value >= 0, but 0.02 to 0.2
+ is a reasonable range. Lower values will cause more frames to be considered
+ unvoiced (in the extreme case of `threshold=0`, almost all frames will be unvoiced).
+ Default: 0.1
+
+ Returns
+ -------
+ f0 : ndarray
+ Estimated F0 contour.
+ temporal_positions : ndarray
+ Temporal position of each frame.
+ """
+ ...
+
+def harvest(
+ x: np.ndarray,
+ fs: int,
+ f0_floor: float = ...,
+ f0_ceil: float = ...,
+ frame_period: float = ...,
+) -> Tuple[np.ndarray, np.ndarray]:
+ """Harvest F0 extraction algorithm.
+
+ Parameters
+ ----------
+ x : ndarray
+ Input waveform signal.
+ fs : int
+ Sample rate of input signal in Hz.
+ f0_floor : float
+ Lower F0 limit in Hz.
+ Default: 71.0
+ f0_ceil : float
+ Upper F0 limit in Hz.
+ Default: 800.0
+ frame_period : float
+ Period between consecutive frames in milliseconds.
+ Default: 5.0
+
+ Returns
+ -------
+ f0 : ndarray
+ Estimated F0 contour.
+ temporal_positions : ndarray
+ Temporal position of each frame.
+ """
+ ...
+
+def stonemask(
+ x: np.ndarray, f0: np.ndarray, temporal_positions: np.ndarray, fs: int
+) -> np.ndarray:
+ """StoneMask F0 refinement algorithm.
+
+ Parameters
+ ----------
+ x : ndarray
+ Input waveform signal.
+ f0 : ndarray
+ Input F0 contour.
+ temporal_positions : ndarray
+ Temporal positions of each frame.
+ fs : int
+ Sample rate of input signal in Hz.
+
+ Returns
+ -------
+ refined_f0 : ndarray
+ Refined F0 contour.
+ """
+ ...
+
+def get_cheaptrick_fft_size(fs: int, f0_floor: float = ...) -> int:
+ """Calculate suitable FFT size for CheapTrick given F0 floor.
+
+ Parameters
+ ----------
+ fs : int
+ Sample rate of input signal in Hz.
+ f0_floor : float
+ Lower F0 limit in Hz. The required FFT size is a direct
+ consequence of the F0 floor used.
+ Default: 71.0
+
+ Returns
+ -------
+ fft_size : int
+ Resulting FFT size.
+ """
+ ...
+
+def get_cheaptrick_f0_floor(fs: int, fft_size: int) -> float:
+ """Calculates actual lower F0 limit for CheapTrick
+ based on the sampling frequency and FFT size used. Whenever F0 is below
+ this threshold the spectrum will be analyzed as if the frame is unvoiced
+ (using kDefaultF0 defined in constantnumbers.h).
+
+ Parameters
+ ----------
+ fs : int
+ Sample rate of input signal in Hz.
+ fft_size : int
+ FFT size used for CheapTrick.
+
+ Returns
+ -------
+ f0_floor : float
+ Resulting lower F0 limit in Hz.
+ """
+ ...
+
+def cheaptrick(
+ x: np.ndarray,
+ f0: np.ndarray,
+ temporal_positions: np.ndarray,
+ fs: int,
+ q1: float = ...,
+ f0_floor: float = ...,
+ fft_size: Optional[int] = ...,
+) -> np.ndarray:
+ """CheapTrick harmonic spectral envelope estimation algorithm.
+
+ Parameters
+ ----------
+ x : ndarray
+ Input waveform signal.
+ f0 : ndarray
+ Input F0 contour.
+ temporal_positions : ndarray
+ Temporal positions of each frame.
+ fs : int
+ Sample rate of input signal in Hz.
+ q1 : float
+ Spectral recovery parameter.
+ Default: -0.15 (this value was tuned and normally does not need adjustment)
+ f0_floor : float, None
+ Lower F0 limit in Hz. Not used in case `fft_size` is specified.
+ Default: 71.0
+ fft_size : int, None
+ FFT size to be used. When `None` (default) is used, the FFT size is computed
+ automatically as a function of the given input sample rate and F0 floor.
+ When `fft_size` is specified, the given `f0_floor` parameter is ignored.
+ Default: None
+
+ Returns
+ -------
+ spectrogram : ndarray
+ Spectral envelope (squared magnitude).
+ """
+ ...
+
+def d4c(
+ x: np.ndarray,
+ f0: np.ndarray,
+ temporal_positions: np.ndarray,
+ fs: int,
+ threshold: float = ...,
+ fft_size: Optional[int] = ...,
+) -> np.ndarray:
+ """D4C aperiodicity estimation algorithm.
+
+ Parameters
+ ----------
+ x : ndarray
+ Input waveform signal.
+ f0 : ndarray
+ Input F0 contour.
+ temporal_positions : ndarray
+ Temporal positions of each frame.
+ fs : int
+ Sample rate of input signal in Hz.
+ threshold : float
+ Threshold for aperiodicity-based voiced/unvoiced decision, in range 0 to 1.
+ If a value of 0 is used, voiced frames will be kept voiced. If a value > 0 is
+ used some voiced frames can be considered unvoiced by setting their aperiodicity
+ to 1 (thus synthesizing them with white noise). Using `threshold=0` will result
+ in the behavior of older versions of D4C. The current default of 0.85 is meant
+ to be used in combination with the Harvest F0 estimator, which was designed to have
+ a high voiced/unvoiced threshold (i.e. most frames will be considered voiced).
+ Default: 0.85
+ fft_size : int, None
+ FFT size to be used. When `None` (default) is used, the FFT size is computed
+ automatically as a function of the given input sample rate and the default F0 floor.
+ When `fft_size` is specified, it should match the FFT size used to compute
+ the spectral envelope (i.e. `fft_size=2*(sp.shape[1] - 1)`) in order to get the
+ desired results when resynthesizing.
+ Default: None
+
+ Returns
+ -------
+ aperiodicity : ndarray
+ Aperiodicity (envelope, linear magnitude relative to spectral envelope).
+ """
+ ...
+
+def synthesize(
+ f0: np.ndarray,
+ spectrogram: np.ndarray,
+ aperiodicity: np.ndarray,
+ fs: int,
+ frame_period: float = ...,
+) -> np.ndarray:
+ """WORLD synthesis from parametric representation.
+
+ Parameters
+ ----------
+ f0 : ndarray
+ Input F0 contour.
+ spectrogram : ndarray
+ Spectral envelope.
+ aperiodicity : ndarray
+ Aperodicity envelope.
+ fs : int
+ Sample rate of input signal in Hz.
+ frame_period : float
+ Period between consecutive frames in milliseconds.
+ Default: 5.0
+
+ Returns
+ -------
+ y : ndarray
+ Output waveform signal.
+ """
+ ...
+
+def get_num_aperiodicities(fs: int) -> int:
+ """Calculate the required dimensionality to code D4C aperiodicity.
+
+ Parameters
+ ----------
+ fs : int
+ Sample rate of input signal in Hz.
+
+ Returns
+ -------
+ n_aper : int
+ Required number of coefficients.
+ """
+ ...
+
+def code_aperiodicity(aperiodicity: np.ndarray, fs: int) -> np.ndarray:
+ """Reduce dimensionality of D4C aperiodicity.
+
+ Parameters
+ ----------
+ aperiodicity : ndarray
+ Aperodicity envelope.
+ fs : int
+ Sample rate of input signal in Hz.
+
+ Returns
+ -------
+ coded_aperiodicity : ndarray
+ Coded aperiodicity envelope.
+ """
+ ...
+
+def decode_aperiodicity(
+ coded_aperiodicity: np.ndarray, fs: int, fft_size: int
+) -> np.ndarray:
+ """Restore full dimensionality of coded D4C aperiodicity.
+
+ Parameters
+ ----------
+ coded_aperiodicity : ndarray
+ Coded aperodicity envelope.
+ fs : int
+ Sample rate of input signal in Hz.
+ fft_size : int
+ FFT size corresponding to the full dimensional aperiodicity.
+
+ Returns
+ -------
+ aperiodicity : ndarray
+ Aperiodicity envelope.
+ """
+ ...
+
+def code_spectral_envelope(
+ spectrogram: np.ndarray, fs: int, number_of_dimensions: int
+) -> np.ndarray:
+ """Reduce dimensionality of spectral envelope.
+
+ Parameters
+ ----------
+ spectrogram : ndarray
+ Spectral envelope.
+ fs : int
+ Sample rate of input signal in Hz.
+ number_of_dimensions : int
+ Number of dimentions of coded spectral envelope
+
+ Returns
+ -------
+ coded_spectral_envelope : ndarray
+ Coded spectral envelope.
+ """
+ ...
+
+def decode_spectral_envelope(
+ coded_spectral_envelope: np.ndarray, fs: int, fft_size: int
+) -> np.ndarray:
+ """Restore full dimensionality of coded spectral envelope.
+
+ Parameters
+ ----------
+ coded_spectral_envelope : ndarray
+ Coded spectral envelope.
+ fs : int
+ Sample rate of input signal in Hz.
+ fft_size : int
+ FFT size corresponding to the full dimensional spectral envelope.
+
+ Returns
+ -------
+ spectrogram : ndarray
+ Spectral envelope.
+ """
+ ...
+
+def wav2world(
+ x: np.ndarray, fs: int, fft_size: Optional[int] = ..., frame_period: float = ...
+) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
+ """Convenience function to do all WORLD analysis steps in a single call.
+
+ In this case only `frame_period` can be configured and other parameters
+ are fixed to their defaults. Likewise, F0 estimation is fixed to
+ DIO plus StoneMask refinement.
+
+ Parameters
+ ----------
+ x : ndarray
+ Input waveform signal.
+ fs : int
+ Sample rate of input signal in Hz.
+ fft_size : int
+ Length of Fast Fourier Transform (in number of samples)
+ The resulting dimension of `ap` adn `sp` will be `fft_size` // 2 + 1
+ frame_period : float
+ Period between consecutive frames in milliseconds.
+ Default: 5.0
+
+ Returns
+ -------
+ f0 : ndarray
+ F0 contour.
+ sp : ndarray
+ Spectral envelope.
+ ap : ndarray
+ Aperiodicity.
+ """
+ ...
diff --git a/pyworld/pyworld.pyx b/pyworld/pyworld.pyx
index e81089c..4cbe93b 100644
--- a/pyworld/pyworld.pyx
+++ b/pyworld/pyworld.pyx
@@ -1,4 +1,5 @@
from __future__ import division
+cimport cython
import cython
import numpy as np
@@ -11,7 +12,7 @@ cdef extern from "world/synthesis.h":
int f0_length, const double * const *spectrogram,
const double * const *aperiodicity,
int fft_size, double frame_period,
- int fs, int y_length, double *y) except + nogil
+ int fs, int y_length, double *y) nogil except +
cdef extern from "world/cheaptrick.h":
@@ -25,7 +26,7 @@ cdef extern from "world/cheaptrick.h":
void InitializeCheapTrickOption(int fs, CheapTrickOption *option) except +
void CheapTrick(const double *x, int x_length, int fs, const double *temporal_positions,
const double *f0, int f0_length, const CheapTrickOption *option,
- double **spectrogram) except + nogil
+ double **spectrogram) nogil except +
cdef extern from "world/dio.h":
@@ -40,7 +41,7 @@ cdef extern from "world/dio.h":
void InitializeDioOption(DioOption *option) except +
int GetSamplesForDIO(int fs, int x_length, double frame_period)
void Dio(const double *x, int x_length, int fs, const DioOption *option,
- double *temporal_positions, double *f0) except + nogil
+ double *temporal_positions, double *f0) nogil except +
cdef extern from "world/harvest.h":
@@ -52,7 +53,7 @@ cdef extern from "world/harvest.h":
void InitializeHarvestOption(HarvestOption *option)
int GetSamplesForHarvest(int fs, int x_length, double frame_period)
void Harvest(const double *x, int x_length, int fs, const HarvestOption *option,
- double *temporal_positions, double *f0) except + nogil
+ double *temporal_positions, double *f0) nogil except +
cdef extern from "world/d4c.h":
@@ -62,13 +63,13 @@ cdef extern from "world/d4c.h":
void InitializeD4COption(D4COption *option) except +
void D4C(const double *x, int x_length, int fs, const double *temporal_positions,
const double *f0, int f0_length, int fft_size, const D4COption *option,
- double **aperiodicity) except + nogil
+ double **aperiodicity) nogil except +
cdef extern from "world/stonemask.h":
void StoneMask(const double *x, int x_length, int fs,
const double *temporal_positions, const double *f0, int f0_length,
- double *refined_f0) except + nogil
+ double *refined_f0) nogil except +
cdef extern from "world/codec.h":
@@ -147,7 +148,7 @@ def dio(np.ndarray[double, ndim=1, mode="c"] x not None, int fs,
np.zeros(f0_length, dtype=np.dtype('float64'))
cdef np.ndarray[double, ndim=1, mode="c"] temporal_positions = \
np.zeros(f0_length, dtype=np.dtype('float64'))
- with (nogil, cython.boundscheck(False)):
+ with nogil, cython.boundscheck(False):
Dio(&x[0], x_length, fs, &option, &temporal_positions[0], &f0[0])
return f0, temporal_positions
@@ -191,7 +192,7 @@ def harvest(np.ndarray[double, ndim=1, mode="c"] x not None, int fs,
np.zeros(f0_length, dtype=np.dtype('float64'))
cdef np.ndarray[double, ndim=1, mode="c"] temporal_positions = \
np.zeros(f0_length, dtype=np.dtype('float64'))
- with (nogil, cython.boundscheck(False)):
+ with nogil, cython.boundscheck(False):
Harvest(&x[0], x_length, fs, &option, &temporal_positions[0], &f0[0])
return f0, temporal_positions
@@ -222,7 +223,7 @@ def stonemask(np.ndarray[double, ndim=1, mode="c"] x not None,
cdef int f0_length = len(f0)
cdef np.ndarray[double, ndim=1, mode="c"] refined_f0 = \
np.zeros(f0_length, dtype=np.dtype('float64'))
- with (nogil, cython.boundscheck(False)):
+ with nogil, cython.boundscheck(False):
StoneMask(&x[0], x_length, fs, &temporal_positions[0],
&f0[0], f0_length, &refined_f0[0])
return refined_f0
@@ -322,7 +323,7 @@ def cheaptrick(np.ndarray[double, ndim=1, mode="c"] x not None,
cdef np.intp_t[:] tmp = np.zeros(f0_length, dtype=np.intp)
cdef double **cpp_spectrogram = ( &tmp[0])
cdef np.intp_t i
- with (nogil, cython.boundscheck(False)):
+ with nogil, cython.boundscheck(False):
for i in range(f0_length):
cpp_spectrogram[i] = &spectrogram[i, 0]
@@ -390,7 +391,7 @@ def d4c(np.ndarray[double, ndim=1, mode="c"] x not None,
cdef np.intp_t[:] tmp = np.zeros(f0_length, dtype=np.intp)
cdef double **cpp_aperiodicity = ( &tmp[0])
cdef np.intp_t i
- with (nogil, cython.boundscheck(False)):
+ with nogil, cython.boundscheck(False):
for i in range(f0_length):
cpp_aperiodicity[i] = &aperiodicity[i, 0]
@@ -450,7 +451,7 @@ def synthesize(np.ndarray[double, ndim=1, mode="c"] f0 not None,
cdef double **cpp_spectrogram = ( &tmp[0])
cdef double **cpp_aperiodicity = ( &tmp2[0])
cdef np.intp_t i
- with (nogil, cython.boundscheck(False)):
+ with nogil, cython.boundscheck(False):
for i in range(f0_length):
cpp_spectrogram[i] = &spectrogram0[i, 0]
cpp_aperiodicity[i] = &aperiodicity0[i, 0]
diff --git a/requirements.txt b/requirements.txt
index c397591..329bcdb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,2 @@
-numpy
-matplotlib
-argparse; python_version<"3.5"
-soundfile
+numpy
+argparse; python_version<"3.5"
diff --git a/setup.py b/setup.py
index d60ce23..77255e2 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
from setuptools.command.build_ext import build_ext
-_VERSION = '0.3.4'
+_VERSION = '0.3.6'
world_src_top = join("lib", "World", "src")
@@ -32,7 +32,11 @@
cmdclass={'build_ext': build_ext},
version=_VERSION,
packages=find_packages(),
- install_requires=['numpy'],
+ package_data={"pyworld": ["py.typed", "*.pyi"]},
+ install_requires=[
+ 'numpy',
+ 'importlib-metadata; python_version<"3.8"',
+ ],
extras_require={
'test': ['nose'],
'sdist': ['numpy', 'cython>=0.24'],