From 2d6836259ab6b7fdc9ceb651bdb703bc0e72028b Mon Sep 17 00:00:00 2001 From: JeremyCCHsu Date: Tue, 18 Aug 2026 22:51:04 +0800 Subject: [PATCH 1/5] Update lib/World submodule to latest commit, bump version to 0.3.6, fix Cython syntax --- lib/World | 2 +- pyworld/pyworld.pyx | 25 +++++++++++++------------ setup.py | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) 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/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/setup.py b/setup.py index d60ce23..6d10ad2 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") From 8796e0d80ce7e130449d81d268703337e5f7fa34 Mon Sep 17 00:00:00 2001 From: JeremyCCHsu Date: Tue, 18 Aug 2026 23:14:22 +0800 Subject: [PATCH 2/5] Merge requests from open PRs (#96, #99, #103, #104, #105, #106) - Add PEP 561 type stubs (pyworld/pyworld.pyi, pyworld/py.typed) and package_data in setup.py (#104, #105) - Migrate from pkg_resources to importlib.metadata with fallback (#99, #103) - Add Cython <3.1.0 constraint for Python <3.9 in pyproject.toml (#106) - Clean requirements.txt to core dependencies and update AppVeyor demo setup (#103) - Add sabonerune to README acknowledgements (#103) --- README.md | 2 +- appveyor.yml | 1 + pyproject.toml | 1 + pyworld/__init__.py | 12 +- pyworld/py.typed | 1 + pyworld/pyworld.pyi | 396 ++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 6 +- setup.py | 6 +- 8 files changed, 417 insertions(+), 8 deletions(-) create mode 100644 pyworld/py.typed create mode 100644 pyworld/pyworld.pyi 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..a28d9b9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -82,6 +82,7 @@ build_script: - pip install -e . test_script: + - pip install soundfile matplotlib - ps: | $ErrorActionPreference = "Stop" Set-Location "$env:APPVEYOR_BUILD_FOLDER\demo" 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/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 6d10ad2..77255e2 100644 --- a/setup.py +++ b/setup.py @@ -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'], From 6016712c786418a791949c1ea0ce587f3fe0f4c2 Mon Sep 17 00:00:00 2001 From: JeremyCCHsu Date: Tue, 18 Aug 2026 23:18:25 +0800 Subject: [PATCH 3/5] add 3.12 & 3.13 tests --- appveyor.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index a28d9b9..6661622 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -48,6 +48,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) From 5909f308937fd856aca3c122b3d6d65cd4a79da2 Mon Sep 17 00:00:00 2001 From: JeremyCCHsu Date: Wed, 19 Aug 2026 11:09:05 +0800 Subject: [PATCH 4/5] Pin libsndfile version for Py 3.6/3.7 --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6661622..9c5432b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -102,11 +102,11 @@ build_script: - pip install -e . test_script: - - pip install soundfile matplotlib + - pip install "soundfile<0.13; python_version < '3.8'" "soundfile; python_version >= '3.8'" 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: From b545454af40553ed74583cb8773ba53419adee93 Mon Sep 17 00:00:00 2001 From: JeremyCCHsu Date: Wed, 19 Aug 2026 17:04:33 +0800 Subject: [PATCH 5/5] Another attempt to fix 3.6/3.7 --- appveyor.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9c5432b..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: @@ -90,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: | @@ -102,7 +108,8 @@ build_script: - pip install -e . test_script: - - pip install "soundfile<0.13; python_version < '3.8'" "soundfile; python_version >= '3.8'" matplotlib + - pip install "soundfile<0.13; python_version < '3.8'" "soundfile; python_version >= '3.8'" + - "pip install --only-binary=:all: matplotlib" - ps: | Set-Location "$env:APPVEYOR_BUILD_FOLDER\demo" python demo.py