mirror of https://github.com/McSinyx/palace
Compare commits
55 Commits
Author | SHA1 | Date |
---|---|---|
Nguyễn Gia Phong | 566911efad | |
Ngô Ngọc Đức Huy | 708f23b35a | |
Nguyễn Gia Phong | c5861833ab | |
MostDeadDeveloper | 1ba92b7c50 | |
Nguyễn Gia Phong | 74a9989e53 | |
Nguyễn Gia Phong | fae0848283 | |
Nguyễn Gia Phong | 90528cee2d | |
Nguyễn Gia Phong | 848421353d | |
Nguyễn Gia Phong | e576dc72ab | |
Nguyễn Gia Phong | 02d4889138 | |
Nguyễn Gia Phong | 9a1dbf5d94 | |
Huy-Ngo | 08408c56c7 | |
Nguyễn Gia Phong | a0b4a90f79 | |
Huy Ngo | fa513ea096 | |
Ngô Ngọc Đức Huy | 4caaee5727 | |
Nguyễn Gia Phong | 11cc59fc86 | |
Huy Ngo | 625b166261 | |
Nguyễn Gia Phong | 7fd5d49b3a | |
Nguyễn Gia Phong | f3da7f8e24 | |
Nguyễn Gia Phong | a8e039a9f0 | |
Huy-Ngo | 92f2454b96 | |
Nguyễn Gia Phong | 4e007b4518 | |
Nguyễn Gia Phong | c2a848cf6c | |
Nguyễn Gia Phong | 2e39a68865 | |
Nguyễn Gia Phong | 587179fecf | |
Francesco Caliumi | 3a9e23917f | |
Nguyễn Gia Phong | c78f14fb42 | |
Nguyễn Gia Phong | 6f00f7046a | |
Nguyễn Gia Phong | c216f307ef | |
Nguyễn Gia Phong | c24df3143b | |
Ngô Xuân Minh | 8285eb06c5 | |
Nguyễn Gia Phong | 83315de9c8 | |
Ngô Ngọc Đức Huy | 24192360f8 | |
Nguyễn Gia Phong | 292ef0393d | |
Ngô Xuân Minh | c59d0ec169 | |
Ngô Xuân Minh | b39aaa9903 | |
Nguyễn Gia Phong | 11cda099a3 | |
Nguyễn Gia Phong | a2444f0eab | |
Nguyễn Gia Phong | 5406740517 | |
Nguyễn Gia Phong | 7ff1d8f1d7 | |
Ngô Xuân Minh | e62989fa2b | |
Huy Ngo | 1aa054e8ed | |
Huy Ngo | 4f98c8e343 | |
Huy Ngo | 8016b78167 | |
Huy Ngo | b884a07903 | |
Huy Ngo | fdec565aaf | |
Ngô Ngọc Đức Huy | b2329a1428 | |
Nguyễn Gia Phong | 9de664a750 | |
Ngô Ngọc Đức Huy | 88c5b0b57a | |
Nguyễn Gia Phong | 5ce35416c7 | |
Nguyễn Gia Phong | 2218d192cb | |
Nguyễn Gia Phong | d575e5e15a | |
Nguyễn Gia Phong | e95dad16bf | |
Nguyễn Gia Phong | 944dd067eb | |
Ngô Ngọc Đức Huy | 6148318585 |
|
@ -1,7 +1,7 @@
|
|||
#!/bin/sh
|
||||
set -ev
|
||||
set -ex
|
||||
git clone --depth 1 https://github.com/kcat/alure /tmp/alure
|
||||
cd /tmp/alure/build
|
||||
OPENALDIR=$(brew --prefix openal-soft) cmake -DCMAKE_FIND_FRAMEWORK=NEVER ..
|
||||
cmake --build . --config Release
|
||||
sudo cmake --install .
|
||||
OPENALDIR=$(brew --prefix openal-soft) cmake -DCMAKE_FIND_FRAMEWORK=NEVER \
|
||||
-S /tmp/alure -B /tmp/alure/build
|
||||
sudo cmake --build /tmp/alure/build --parallel $(sysctl -n hw.ncpu) \
|
||||
--config Release --target install
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/sh
|
||||
set -ev
|
||||
set -ex
|
||||
yum install -y git cmake pulseaudio \
|
||||
alsa-lib-devel pulseaudio-libs-devel jack-audio-connection-kit-devel \
|
||||
libvorbis-devel opusfile-devel libsndfile-devel
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/sh
|
||||
set -ex
|
||||
cd $(mktemp -d)
|
||||
unzip $1
|
||||
DYLD_FALLBACK_LIBRARY_PATH=/usr/local/lib delocate-path -L palace.dylibs .
|
||||
wheel=$(basename $1)
|
||||
zip -r $wheel *
|
||||
mkdir -p $2
|
||||
mv $wheel $2
|
||||
tempdir=$(pwd)
|
||||
cd -
|
||||
rm -rf $tempdir
|
|
@ -0,0 +1,12 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: __huy_ngo__
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: McSinyx
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
@ -0,0 +1,48 @@
|
|||
name: Deploy documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/*
|
||||
- docs/**
|
||||
- src/**
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Checkout palace
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Checkout alure
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: kcat/alure
|
||||
path: alure
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt install \
|
||||
cmake libopenal-dev libvorbis-dev libopusfile-dev libsndfile1-dev
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr -S alure -B alure/build
|
||||
sudo cmake --build alure/build --parallel `nproc` --target install
|
||||
rm -fr alure
|
||||
python -m pip install Sphinx sphinx_rtd_theme .
|
||||
|
||||
- name: Build site
|
||||
working-directory: docs
|
||||
run: make html
|
||||
|
||||
- name: Deploy site
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./docs/build/html
|
|
@ -0,0 +1,28 @@
|
|||
name: Quick check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install tox
|
||||
run: python -m pip install tox
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Main check
|
||||
run: python -m tox -e lint
|
|
@ -138,3 +138,6 @@ dmypy.json
|
|||
\#*\#
|
||||
.\#*
|
||||
*~
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
|
24
.travis.yml
24
.travis.yml
|
@ -1,20 +1,20 @@
|
|||
language: python
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^\d+(\.\d+)+((a|b|rc)\d+)?(\.post\d+)?(\.dev\d+)?$/
|
||||
|
||||
language: python
|
||||
|
||||
env:
|
||||
global:
|
||||
- TWINE_USERNAME=__token__
|
||||
- MACOSX_DEPLOYMENT_TARGET=10.9
|
||||
- CIBW_MANYLINUX_X86_64_IMAGE=manylinux2014
|
||||
- CIBW_BEFORE_BUILD_LINUX=.ci/before-build-manylinux2014
|
||||
- CIBW_BEFORE_BUILD_MACOS=.ci/before-build-macos
|
||||
- CIBW_BEFORE_BUILD_LINUX=.ci/before-build-manylinux2014
|
||||
- CIBW_MANYLINUX_X86_64_IMAGE=manylinux2014
|
||||
- CIBW_REPAIR_WHEEL_COMMAND_MACOS=".ci/repair-whl-macos {wheel} {dest_dir}"
|
||||
- CIBW_TEST_REQUIRES=tox
|
||||
- CIBW_TEST_COMMAND_MACOS="tox -c /Users/travis/build/McSinyx/palace"
|
||||
- CIBW_TEST_COMMAND_LINUX="tox -c /project"
|
||||
- CIBW_TEST_COMMAND="tox -c {project}"
|
||||
|
||||
addons:
|
||||
homebrew:
|
||||
|
@ -32,16 +32,14 @@ jobs:
|
|||
osx_image: xcode11.3
|
||||
language: shell
|
||||
env: CIBW_BUILD=cp36-macosx_x86_64
|
||||
- os: osx
|
||||
osx_image: xcode11.3
|
||||
language: shell
|
||||
env: CIBW_BUILD=cp37-macosx_x86_64
|
||||
- services: docker
|
||||
env: CIBW_BUILD=cp36-manylinux_x86_64
|
||||
env: CIBW_BUILD="cp36-manylinux_x86_64 cp36-manylinux_aarch64"
|
||||
- services: docker
|
||||
env: CIBW_BUILD=cp37-manylinux_x86_64
|
||||
env: CIBW_BUILD="cp37-manylinux_x86_64 cp37-manylinux_aarch64"
|
||||
- services: docker
|
||||
env: CIBW_BUILD=cp38-manylinux_x86_64
|
||||
env: CIBW_BUILD="cp38-manylinux_x86_64 cp38-manylinux_aarch64"
|
||||
- services: docker
|
||||
env: CIBW_BUILD="cp39-manylinux_x86_64 cp39-manylinux_aarch64"
|
||||
|
||||
script: python3 -m cibuildwheel --output-dir=dist
|
||||
|
||||
|
|
11
MANIFEST.in
11
MANIFEST.in
|
@ -1 +1,10 @@
|
|||
include src/*.pxd src/*.pyx src/*.h tests/*.py examples/*.py CMakeLists.txt
|
||||
include CMakeLists.txt
|
||||
recursive-include src *.h *.pxd *.pyx
|
||||
|
||||
graft docs
|
||||
prune docs/build
|
||||
|
||||
include tox.ini
|
||||
recursive-include tests *.py
|
||||
recursive-include examples *.py
|
||||
graft tests/data
|
||||
|
|
37
README.md
37
README.md
|
@ -10,12 +10,12 @@ To quote alure's README,
|
|||
In some sense, what palace aimes to be to [OpenAL Soft] is what [ModernGL]
|
||||
is to OpenGL (except that all the heavy-lifting are taken are by alure):
|
||||
|
||||
* 3D sound rendering
|
||||
* Environmental audio effects: reverb, atmospheric air absorption,
|
||||
* 3D positional sound rendering
|
||||
* Environmental effects: reverb, atmospheric air absorption,
|
||||
sound occlusion and obstruction
|
||||
* Binaural (HRTF) rendering
|
||||
* Out-of-the-box audio decoding of FLAC, MP3, Ogg Vorbis, Opus, WAV, AIFF, etc.
|
||||
* Modern Pythonic API: snake_case, `@property`, `with` context manager,
|
||||
* Modern Pythonic API: `snake_case`, `@property`, `with` context manager,
|
||||
type annotation
|
||||
|
||||
## Installation
|
||||
|
@ -28,8 +28,8 @@ Palace can be install from the [Python Package Index][PyPI] via simply
|
|||
pip install palace
|
||||
|
||||
Wheel distributions are built exclusively for amd64. Currently, only GNU/Linux
|
||||
is properly supported. If you want to help packaging for Windows and macOS,
|
||||
see [GH-1] and [GH-63] respectively on our issues tracker on GitHub.
|
||||
and macOS are properly supported. If you want to help packaging for Windows,
|
||||
please see [GH-1] on our issues tracker on GitHub.
|
||||
|
||||
### From source
|
||||
Aside from the build dependencies listed in `pyproject.toml`, one will
|
||||
|
@ -44,23 +44,19 @@ One may start with the `examples` for sample usage of palace.
|
|||
For further information, Python's `help` is your friend and
|
||||
the API is also available for [online reference][API].
|
||||
|
||||
## Contributing
|
||||
Our documentation contains [a brief guide][contrib] which may help you
|
||||
get started with the development. We also think that you might find
|
||||
[our design principles][design] appealing as well.
|
||||
|
||||
## License and Credits
|
||||
Palace is free software: you can redistribute it and/or modify it
|
||||
under the terms of the [GNU Lesser General Public License][LGPLv3+]
|
||||
as published by the Free Software Foundation, either version 3
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
To ensure that palace can run without any dependencies outside of the [pip]
|
||||
toolchain, the wheels are bundled with dynamically linked libraries from
|
||||
the build machine, which is similar to static linking:
|
||||
|
||||
| Library | License |
|
||||
| -------------- | ------------ |
|
||||
| [Alure][alure] | ZLib |
|
||||
| [OpenAL Soft] | GNU LGPLv2+ |
|
||||
| [Vorbis] | 3-clause BSD |
|
||||
| [Opus] | 3-clause BSD |
|
||||
| [libsndfile] | GNU LGPL2.1+ |
|
||||
[The full list of works bundled with palace and other credits][copying]
|
||||
can be found in our documentation.
|
||||
|
||||
[alure]: https://github.com/kcat/alure
|
||||
[OpenAL Soft]: https://kcat.strangesoft.net/openal.html
|
||||
|
@ -69,10 +65,9 @@ the build machine, which is similar to static linking:
|
|||
[pip]: https://pip.pypa.io/en/latest/
|
||||
[PyPI]: https://pypi.org/project/palace/
|
||||
[GH-1]: https://github.com/McSinyx/palace/issues/1
|
||||
[GH-63]: https://github.com/McSinyx/palace/issues/63
|
||||
[CMake]: https://cmake.org/
|
||||
[API]: https://mcsinyx.github.io/palace/html/reference.html
|
||||
[API]: https://mcsinyx.github.io/palace/reference.html
|
||||
[contrib]: https://mcsinyx.github.io/palace/contributing.html
|
||||
[design]: https://mcsinyx.github.io/palace/design.html
|
||||
[LGPLv3+]: https://www.gnu.org/licenses/lgpl-3.0.en.html
|
||||
[Vorbis]: https://xiph.org/vorbis/
|
||||
[Opus]: http://opus-codec.org/
|
||||
[libsndfile]: http://www.mega-nerd.com/libsndfile/
|
||||
[copying]: https://mcsinyx.github.io/palace/copying.html
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
|
||||
# You can set these variables from the command line,
|
||||
# and also from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@ -0,0 +1,35 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
|
@ -0,0 +1,36 @@
|
|||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options.
|
||||
# For a full list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# Project information
|
||||
project = 'palace'
|
||||
copyright = '2019, 2020 Nguyễn Gia Phong et al'
|
||||
author = 'Nguyễn Gia Phong et al.'
|
||||
release = '0.2.1'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings.
|
||||
# They can be extensions coming with Sphinx (named 'sphinx.ext.*')
|
||||
# or your custom ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc', 'sphinx.ext.githubpages', 'sphinx.ext.napoleon']
|
||||
napoleon_google_docstring = False
|
||||
default_role = 'py:obj'
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match
|
||||
# files and directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = []
|
||||
|
||||
# Options for HTML output
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets)
|
||||
# here, relative to this directory. They are copied after the builtin
|
||||
# static files, so a file named "default.css" will overwrite the builtin
|
||||
# "default.css".
|
||||
html_static_path = []
|
|
@ -0,0 +1,196 @@
|
|||
Getting Involved
|
||||
================
|
||||
|
||||
.. note:: The development of palace is carried out on GitHub_.
|
||||
Since GitHub is not free software, we fully understand
|
||||
if one does not want to register an account just to participate
|
||||
in our development. Therefore, we also welcome patches
|
||||
and bug reports sent via email.
|
||||
|
||||
First of all, thank you for using and contributing to palace! We welcome
|
||||
all forms of contribution, and `the mo the merier`_. By saying this, we also
|
||||
mean that we much prefer receiving many small and self-contained bug reports,
|
||||
feature requests and patches than a giant one. There is no limit for
|
||||
the number of contributions one may or should make. While it may seem
|
||||
appealing to be able to dump all thoughts and feelings into one ticket,
|
||||
it would be more difficult for us to keep track of the progress.
|
||||
|
||||
Reporting a Bug
|
||||
---------------
|
||||
|
||||
Before filing a bug report, please make sure that the bug has not been
|
||||
already reported by searching our GitHub Issues_ tracker.
|
||||
|
||||
To facilitate the debugging process, a bug report should at least contain
|
||||
the following information:
|
||||
|
||||
#. The platform, the CPython version and the compiler used to build it.
|
||||
These can be obtained from :py:func:`platform.platform`,
|
||||
:py:func:`platform.python_version` and :py:func:`platform.python_compiler`,
|
||||
respectively.
|
||||
#. The version of palace and how you installed it.
|
||||
The earlier is usually provided by ``pip show palace``.
|
||||
#. Detailed instructions on how to reproduce the bug,
|
||||
for example a short Python script would be appreciated.
|
||||
|
||||
Requesting a Feature
|
||||
--------------------
|
||||
|
||||
Prior to filing a feature request, please make sure that the feature
|
||||
has not been already reported by searching our GitHub Issues_ tracker.
|
||||
|
||||
Please only ask for features that you (or an incapacitated friend
|
||||
you can personally talk to) require. Do not request features because
|
||||
they seem like a good idea. If they are really useful, they will be
|
||||
requested by someone who requires them.
|
||||
|
||||
Submitting a Patch
|
||||
------------------
|
||||
|
||||
We accept all kinds of patches, from documentation and CI/CD setup
|
||||
to bug fixes, feature implementations and tests. These are hosted on GitHub
|
||||
and one may create a local repository by running::
|
||||
|
||||
git clone https://github.com/McSinyx/palace
|
||||
|
||||
While the patch can be submitted via email, it is preferable to file
|
||||
a pull request on GitHub against the ``master`` branch to allow more people
|
||||
to review it, since we do not have any mail list. Either way, contributors
|
||||
must have legal permission to distribute the code and it must be available
|
||||
under `LGPLv3+`_. Furthermore, each contributor retains the copyrights
|
||||
of their patch, to ensure that the licence can never be revoked even if
|
||||
others wants to. It is advisable that the author list per legal name
|
||||
under the copyright header of each source file they modify, like so::
|
||||
|
||||
Copyright (C) 2038 Foo Bar
|
||||
|
||||
Using GitHub
|
||||
^^^^^^^^^^^^
|
||||
|
||||
#. Create a fork_ of our repository on GitHub.
|
||||
#. Checkout the source code and (optionally) add the ``upstream`` remote::
|
||||
|
||||
git clone https://github.com/YOUR_GITHUB_USERNAME/palace
|
||||
cd palace
|
||||
git remote add upstream https://github.com/McSinyx/palace
|
||||
|
||||
#. Start working on your patch and make sure your code complies with
|
||||
the `Style Guidelines`_ and passes the test suit run by tox_.
|
||||
#. Add relevant tests to the patch and work on it until they all pass.
|
||||
In case one is only modifying tests, perse may install palace using
|
||||
``CYTHON_TRACE=1 pip install .`` then run pytest_ directly to avoid
|
||||
having to build the extension module multiple times.
|
||||
#. Update the copyright notices of the files you modified.
|
||||
Palace is collectively licensed under `LGPLv3+`_,
|
||||
and to protect the freedom of the users,
|
||||
copyright holders need to be properly documented.
|
||||
#. Add_, commit_ with `a great message`_ then push_ the result.
|
||||
#. Finally, `create a pull request`_. We will then review and merge it.
|
||||
|
||||
It is recommended to create a new branch in your fork
|
||||
(``git checkout -c what-you-are-working-on``) instead of working directly
|
||||
on ``master``. This way one can still sync per fork with our ``master`` branch
|
||||
and ``git pull --rebase upstream master`` to avoid integration issues.
|
||||
|
||||
Via Email
|
||||
^^^^^^^^^
|
||||
|
||||
#. Checkout the source code::
|
||||
|
||||
git clone https://github.com/McSinyx/palace
|
||||
cd palace
|
||||
|
||||
#. Work on your patch with tests and copyright notice included
|
||||
as described above.
|
||||
#. `git-format-patch`_ and send it to one of the maintainers
|
||||
(our emails addresses are available under ``git log``).
|
||||
We will then review and merge it.
|
||||
|
||||
In any case, thank you very much for your contributions!
|
||||
|
||||
Making a Release
|
||||
----------------
|
||||
|
||||
While this is meant for developers doing a palace release, contributors wishing
|
||||
to improve the CI/CD may find it helpful.
|
||||
|
||||
#. Under the local repository, checkout the ``master`` branch
|
||||
and sync with the one on GitHub using ``git pull``.
|
||||
#. Bump the version in ``setup.cfg`` and push to GitHub.
|
||||
#. Create a source distribution by running ``setup.py sdist``.
|
||||
The distribution generated by this command is now referred to as ``sdist``.
|
||||
#. Using twine_, upload the ``sdist`` to PyPI via ``twine upload $sdist``.
|
||||
#. On GitHub, tag a new release with the ``sdist`` attached.
|
||||
In the release note, make sure to include all user-facing changes
|
||||
since the previous release. This will trigger the CD services
|
||||
to build the wheels and publish them to PyPI.
|
||||
#. Wait for the wheel for your platform to arrive to PyPI and install it.
|
||||
Play around with it for a little to make sure that everything is OK.
|
||||
|
||||
Style Guidelines
|
||||
----------------
|
||||
|
||||
Python and Cython
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Generally, palace follows :pep:`8` and :pep:`257`,
|
||||
with the following preferences and exceptions:
|
||||
|
||||
* Hanging indentation is *always* preferred,
|
||||
where continuation lines are indented by 4 spaces.
|
||||
* Comments and one-line docstrings are limited to column 79
|
||||
instead of 72 like for multi-line docstrings.
|
||||
* Cython extern declarations need not follow the 79-character limit.
|
||||
* Break long lines before a binary operator.
|
||||
* Use form feeds sparingly to break long modules
|
||||
into pages of relating functions and classes.
|
||||
* Prefer single-quoted strings over double-quoted strings,
|
||||
unless the string contains single quote characters.
|
||||
* Avoid trailing commas at all costs.
|
||||
* Line breaks within comments and docstrings should not cut a phrase in half.
|
||||
* Everything deserves a docstring. Palace follows numpydoc_ which support
|
||||
documenting attributes as well as constants and module-level variables.
|
||||
In additional to docstrings, type annotations should be employed
|
||||
for all public names.
|
||||
* Use numpydoc_ markups moderately to keep docstrings readable as plain text.
|
||||
|
||||
C++
|
||||
^^^
|
||||
|
||||
C++ codes should follow GNU style, which is best documented at Octave_.
|
||||
|
||||
reStructuredText
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
In order for reStructuredText to be rendered correctly, the body of
|
||||
constructs beginning with a marker (lists, hyperlink targets, comments, etc.)
|
||||
must be aligned relative to the marker. For this reason, it is convenient
|
||||
to set your editor indentation level to 3 spaces, since most constructs
|
||||
starts with two dots and a space. However, be aware of that bullet items
|
||||
require 2-space alignment and other exceptions.
|
||||
|
||||
Limit all lines to a maximum of 79 characters. Similar to comments
|
||||
and docstrings, phrases should not be broken in the middle.
|
||||
The source code of this guide itself is a good example on how line breaks
|
||||
should be handled. Additionally, two spaces should also be used
|
||||
after a sentence-ending period in multi-sentence paragraph,
|
||||
except after the final sentence.
|
||||
|
||||
.. _GitHub: https://github.com/McSinyx/palace
|
||||
.. _the mo the merier:
|
||||
https://www.phrases.org.uk/meanings/the-more-the-merrier.html
|
||||
.. _Issues: https://github.com/McSinyx/palace/issues
|
||||
.. _LGPLv3+: https://www.gnu.org/licenses/lgpl-3.0.en.html
|
||||
.. _fork: https://github.com/McSinyx/palace/fork
|
||||
.. _tox: https://tox.readthedocs.io/en/latest/
|
||||
.. _pytest: https://docs.pytest.org/en/latest/
|
||||
.. _Add: https://git-scm.com/docs/git-add
|
||||
.. _commit: https://git-scm.com/docs/git-commit
|
||||
.. _a great message: https://chris.beams.io/posts/git-commit/#seven-rules
|
||||
.. _push: https://git-scm.com/docs/git-push
|
||||
.. _create a pull request:
|
||||
https://help.github.com/articles/creating-a-pull-request
|
||||
.. _git-format-patch: https://git-scm.com/docs/git-format-patch
|
||||
.. _twine: https://twine.readthedocs.io/en/latest/
|
||||
.. _numpydoc: https://numpydoc.readthedocs.io/en/latest/format.html
|
||||
.. _Octave: https://wiki.octave.org/C%2B%2B_style_guide
|
|
@ -0,0 +1,75 @@
|
|||
Copying
|
||||
=======
|
||||
|
||||
This listing is our best-faith, hard-work effort at accurate attribution,
|
||||
sources, and licenses for everything in palace. If you discover
|
||||
an asset/contribution that is incorrectly attributed or licensed,
|
||||
please contact us immediately. We are happy to do everything we can
|
||||
to fix or remove the issue.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Palace is free software: you can redistribute it and/or modify it
|
||||
under the terms of the `GNU Lesser General Public License`_
|
||||
as published by the Free Software Foundation, either version 3
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
To ensure that palace can run without any dependencies outside of the pip_
|
||||
toolchain, the wheels are bundled with dynamically linked libraries from
|
||||
the build machine, which is similar to static linking:
|
||||
|
||||
============== ============
|
||||
Library License
|
||||
============== ============
|
||||
Alure_ ZLib
|
||||
`OpenAL Soft`_ GNU LGPLv2+
|
||||
Vorbis_ 3-clause BSD
|
||||
Opus_ 3-clause BSD
|
||||
libsndfile_ GNU LGPL2.1+
|
||||
============== ============
|
||||
|
||||
In addition, the following sounds are used for testing:
|
||||
|
||||
=============================================== =========
|
||||
Sound (located in ``tests/data``) License
|
||||
=============================================== =========
|
||||
`164957__zonkmachine__white-noise.ogg`_ CC0 1.0
|
||||
`24741__tim-kahn__b23-c1-raw.aiff`_ CC BY 3.0
|
||||
`261590__kwahmah-02__little-glitch.flac`_ CC BY 3.0
|
||||
`353684__tec-studio__drip2.mp3`_ CC0 1.0
|
||||
`99642__jobro__deconvoluted-20hz-to-20khz.wav`_ CC BY 3.0
|
||||
=============================================== =========
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
Palace would never have seen the light of day without the help from
|
||||
the developers of Alure_ and Cython_ who promptly gave detail answers
|
||||
and made quick fixes to all of our problems.
|
||||
|
||||
The wheels are build using cibuildwheel_, which made building extension modules
|
||||
much less of a painful experience. `Travis CI`_ and AppVeyor_ kindly provides
|
||||
their services free of charge for automated CI/CD.
|
||||
|
||||
This documentation is generated using Sphinx_, whose maintainer responses
|
||||
extreamly quickly to obsolete Cython-related issues.
|
||||
|
||||
.. _GNU Lesser General Public License:
|
||||
https://www.gnu.org/licenses/lgpl-3.0.en.html
|
||||
.. _pip: https://pip.pypa.io/en/latest/
|
||||
.. _Alure: https://github.com/kcat/alure
|
||||
.. _OpenAL Soft: https://kcat.strangesoft.net/openal.html
|
||||
.. _Vorbis: https://xiph.org/vorbis/
|
||||
.. _Opus: https://opus-codec.org/
|
||||
.. _libsndfile: http://www.mega-nerd.com/libsndfile/
|
||||
.. _164957__zonkmachine__white-noise.ogg: https://freesound.org/s/164957/
|
||||
.. _24741__tim-kahn__b23-c1-raw.aiff: https://freesound.org/s/24741/
|
||||
.. _261590__kwahmah-02__little-glitch.flac: https://freesound.org/s/261590/
|
||||
.. _353684__tec-studio__drip2.mp3: https://freesound.org/s/353684/
|
||||
.. _99642__jobro__deconvoluted-20hz-to-20khz.wav: https://freesound.org/s/99642/
|
||||
.. _Cython: https://cython.org/
|
||||
.. _cibuildwheel: https://cibuildwheel.readthedocs.io/en/stable/
|
||||
.. _Sphinx: https://www.sphinx-doc.org/en/master/
|
||||
.. _Travis CI: https://travis-ci.com/
|
||||
.. _AppVeyor: https://www.appveyor.com/
|
|
@ -0,0 +1,185 @@
|
|||
Design Principles
|
||||
=================
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
In this section, we will discuss a few design principles in order to write
|
||||
a safe, efficient, easy-to-use and extendable 3D audio library for Python,
|
||||
by wrapping existing functionalities from the C++ API alure_.
|
||||
|
||||
This part of the documentation assumes its reader are at least familiar with
|
||||
Cython, Python and C++11.
|
||||
|
||||
.. _impl-idiom:
|
||||
|
||||
The Impl Idiom
|
||||
--------------
|
||||
|
||||
*Not to be confused with* `the pimpl idiom`_.
|
||||
|
||||
For memory-safety, whenever possible, we rely on Cython for allocation and
|
||||
deallocation of C++ objects. To do this, the nullary constructor needs to be
|
||||
(re-)declared in Cython, e.g.
|
||||
|
||||
.. code-block:: cython
|
||||
|
||||
cdef extern from 'foobar.h' namespace 'foobar':
|
||||
cdef cppclass Foo:
|
||||
Foo()
|
||||
float meth(size_t crack) except +
|
||||
...
|
||||
|
||||
The Cython extension type can then be declared as follows
|
||||
|
||||
.. code-block:: cython
|
||||
|
||||
cdef class Bar:
|
||||
cdef Foo impl
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.impl = ...
|
||||
|
||||
@staticmethod
|
||||
def from_baz(baz: Baz) -> Bar:
|
||||
bar = Bar.__new__(Bar)
|
||||
bar.impl = ...
|
||||
return bar
|
||||
|
||||
def meth(self, crack: int) -> float:
|
||||
return self.impl.meth(crack)
|
||||
|
||||
The Modern Python
|
||||
-----------------
|
||||
|
||||
One of the goal of palace is to create a Pythonic, i.e. intuitive and concise,
|
||||
interface. To achieve this, we try to make use of some modern Python features,
|
||||
which not only allow users to adopt palace with ease, but also make their
|
||||
programs more readable and less error-prone.
|
||||
|
||||
.. _getter-setter:
|
||||
|
||||
Property Attributes
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A large proportion of alure API are getters/setter methods. In Python,
|
||||
it is a good practice to use property_ to abstract these calls, and thus make
|
||||
the interface more natural with attribute-like referencing and assignments.
|
||||
|
||||
Due to implementation details, Cython has to hijack the ``@property`` decorator
|
||||
to make it work for read-write properties. Unfortunately, the Cython-generated
|
||||
descriptors do not play very well with other builtin decorators, thus in some
|
||||
cases, it is recommended to alias the call to ``property`` as follows
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
getter = property
|
||||
setter = lambda fset: property(fset=fset, doc=fset.__doc__)
|
||||
|
||||
Then ``@getter`` and ``@setter`` can be used to decorate read-only and
|
||||
write-only properties, respectively, without any trouble even if other
|
||||
decorators are used for the same extension type method.
|
||||
|
||||
Context Managers
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
The alure API defines many objects that need manual tear-down in
|
||||
a particular order. Instead of trying to be clever and perform automatic
|
||||
clean-ups at garbage collection, we should put the user in control.
|
||||
To quote *The Zen of Python*,
|
||||
|
||||
| If the implementation is hard to explain, it's a bad idea.
|
||||
| If the implementation is easy to explain, it may be a good idea.
|
||||
|
||||
With that being said, it does not mean we do not provide any level of
|
||||
abstraction. A simplified case in point would be
|
||||
|
||||
.. code-block:: cython
|
||||
|
||||
cdef class Device:
|
||||
cdef alure.Device impl
|
||||
|
||||
def __init__(self, name: str = '') -> None:
|
||||
self.impl = devmgr.open_playback(name)
|
||||
|
||||
def __enter__(self) -> Device:
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc) -> Optional[bool]:
|
||||
self.close()
|
||||
|
||||
def close(self) -> None:
|
||||
self.impl.close()
|
||||
|
||||
Now if the ``with`` statement is used, it will make sure the device
|
||||
will be closed, regardless of whatever may happen within the inner block
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with Device() as dev:
|
||||
...
|
||||
|
||||
as it is equivalent to
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
dev = Device()
|
||||
try:
|
||||
...
|
||||
finally:
|
||||
dev.close()
|
||||
|
||||
Other than closure/destruction of objects, typical uses of `context managers`__
|
||||
also include saving and restoring various kinds of global state (as seen in
|
||||
:py:class:`Context`), locking and unlocking resources, etc.
|
||||
|
||||
__ https://docs.python.org/3/reference/datamodel.html#context-managers
|
||||
|
||||
The Double Reference
|
||||
--------------------
|
||||
|
||||
While wrapping C++ interfaces, :ref:`the impl idiom <impl-idiom>` might not
|
||||
be adequate, since the derived Python methods need to be callable from C++.
|
||||
Luckily, Cython can handle Python objects within C++ classes just fine,
|
||||
although we'll need to handle the reference count ourselves, e.g.
|
||||
|
||||
.. code-block:: cython
|
||||
|
||||
cdef cppclass CppDecoder(alure.BaseDecoder):
|
||||
Decoder pyo
|
||||
|
||||
__init__(Decoder decoder):
|
||||
this.pyo = decoder
|
||||
Py_INCREF(pyo)
|
||||
|
||||
__dealloc__():
|
||||
Py_DECREF(pyo)
|
||||
|
||||
bool seek(uint64_t pos):
|
||||
return pyo.seek(pos)
|
||||
|
||||
With this being done, we can now write the wrapper as simply as
|
||||
|
||||
.. code-block:: cython
|
||||
|
||||
cdef class BaseDecoder:
|
||||
cdef shared_ptr[alure.Decoder] pimpl
|
||||
|
||||
def __cinit__(self, *args, **kwargs) -> None:
|
||||
self.pimpl = shared_ptr[alure.Decoder](new CppDecoder(self))
|
||||
|
||||
def seek(pos: int) -> bool:
|
||||
...
|
||||
|
||||
Because ``__cinit__`` is called by ``__new__``, any Python class derived
|
||||
from ``BaseDecoder`` will be exposed to C++ as an attribute of ``CppDecoder``.
|
||||
Effectively, this means the users can have the alure API calling their
|
||||
inherited Python object as naturally as if palace is implemented in pure Python.
|
||||
|
||||
In practice, :py:class:`BaseDecoder` will also need to take into account
|
||||
other guarding mechanisms like :py:class:`abc.ABC`. Due to Cython limitations,
|
||||
implementation as a pure Python class and :ref:`aliasing <getter-setter>` of
|
||||
``@getter``/``@setter`` should be considered.
|
||||
|
||||
.. _alure: https://github.com/kcat/alure
|
||||
.. _`the pimpl idiom`: https://wiki.c2.com/?PimplIdiom
|
||||
.. _property: https://docs.python.org/3/library/functions.html#property
|
|
@ -0,0 +1,45 @@
|
|||
Overview
|
||||
========
|
||||
|
||||
Pythonic Audio Library and Codecs Environment provides common higher-level API
|
||||
for audio rendering using OpenAL:
|
||||
|
||||
* 3D positional rendering, with HRTF_ support for stereo systems
|
||||
* Environmental effects: reverb, atmospheric air absorption,
|
||||
sound occlusion and obstruction
|
||||
* Out-of-the-box codec support: FLAC, MP3, Ogg Vorbis, Opus, WAV, AIFF, etc.
|
||||
|
||||
Palace wraps around the C++ interface alure_ using Cython_ for a safe and
|
||||
convenient interface with type hinting, data descriptors and context managers,
|
||||
following :pep:`8#naming-conventions` (``PascalCase.snake_case``).
|
||||
|
||||
.. toctree::
|
||||
:caption: Table of Contents
|
||||
:maxdepth: 2
|
||||
|
||||
installation
|
||||
tutorial/index
|
||||
reference/index
|
||||
design
|
||||
contributing
|
||||
copying
|
||||
|
||||
.. toctree::
|
||||
:caption: Quick Navigation
|
||||
:hidden:
|
||||
|
||||
Python Package Index <https://pypi.org/project/palace/>
|
||||
Travis CI Build <https://travis-ci.com/github/McSinyx/palace>
|
||||
AppVeyor Build <https://ci.appveyor.com/project/McSinyx/palace>
|
||||
GitHub Repository <https://github.com/McSinyx/palace>
|
||||
Matrix Chat Room <https://matrix.to/#/#palace-dev:matrix.org>
|
||||
|
||||
Indices and Tables
|
||||
------------------
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`search`
|
||||
|
||||
.. _HRTF: https://en.wikipedia.org/wiki/Head-related_transfer_function
|
||||
.. _alure: https://github.com/kcat/alure
|
||||
.. _Cython: https://cython.org
|
|
@ -0,0 +1,37 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
Palace requires CPython_ version 3.6 or above for runtime
|
||||
and pip_ for installation.
|
||||
|
||||
Via PyPI
|
||||
--------
|
||||
|
||||
Palace can be installed from PyPI::
|
||||
|
||||
pip install palace
|
||||
|
||||
Wheel distributions are built exclusively for amd64. Currently, only GNU/Linux
|
||||
and macOS are properly supported. If you want to help packaging for Windows,
|
||||
please see `GH-1`_ on our issues tracker on GitHub.
|
||||
|
||||
From source
|
||||
-----------
|
||||
|
||||
Aside from the build dependencies listed in ``pyproject.toml``,
|
||||
one will additionally need compatible Python headers, alure_,
|
||||
a C++14 compiler, CMake_ 2.6+ (and probably git_ for fetching the source).
|
||||
Palace can then be compiled and installed by running::
|
||||
|
||||
git clone https://github.com/McSinyx/palace.git
|
||||
pip install palace/
|
||||
|
||||
.. _CPython: https://www.python.org/
|
||||
.. _pip: https://pip.pypa.io/en/latest/
|
||||
.. _GH-1: https://github.com/McSinyx/palace/issues/1
|
||||
.. _alure: https://github.com/kcat/alure
|
||||
.. _CMake: https://cmake.org/
|
||||
.. _git: https://git-scm.com/
|
|
@ -0,0 +1,17 @@
|
|||
Resource Caching
|
||||
================
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
Audio Buffers
|
||||
-------------
|
||||
|
||||
.. autoclass:: Buffer
|
||||
:members:
|
||||
|
||||
Loading & Freeing in Batch
|
||||
--------------------------
|
||||
|
||||
.. autofunction:: cache
|
||||
|
||||
.. autofunction:: free
|
|
@ -0,0 +1,83 @@
|
|||
Audio Library Contexts
|
||||
======================
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
Context and Auxiliary Classes
|
||||
-----------------------------
|
||||
|
||||
.. autoclass:: Context
|
||||
:members:
|
||||
|
||||
.. autoclass:: Listener
|
||||
:members:
|
||||
|
||||
.. autoclass:: MessageHandler
|
||||
:members:
|
||||
|
||||
Using Contexts
|
||||
--------------
|
||||
|
||||
.. autofunction:: use_context
|
||||
|
||||
.. autofunction:: current_context
|
||||
|
||||
.. autofunction:: thread_local
|
||||
|
||||
Context Creation Attributes
|
||||
---------------------------
|
||||
|
||||
.. data:: CHANNEL_CONFIG
|
||||
:type: int
|
||||
|
||||
Context creation key to specify the channel configuration
|
||||
(either ``MONO``, ``STEREO``, ``QUAD``, ``X51``, ``X61`` or ``X71``).
|
||||
|
||||
.. data:: SAMPLE_TYPE
|
||||
:type: int
|
||||
|
||||
Context creation key to specify the sample type
|
||||
(either ``[UNSIGNED_]{BYTE,SHORT,INT}`` or ``FLOAT``).
|
||||
|
||||
.. data:: FREQUENCY
|
||||
:type: int
|
||||
|
||||
Context creation key to specify the frequency in hertz.
|
||||
|
||||
.. data:: MONO_SOURCES
|
||||
:type: int
|
||||
|
||||
Context creation key to specify the number of mono (3D) sources.
|
||||
|
||||
.. data:: STEREO_SOURCES
|
||||
:type: int
|
||||
|
||||
Context creation key to specify the number of stereo sources.
|
||||
|
||||
.. data:: MAX_AUXILIARY_SENDS
|
||||
:type: int
|
||||
|
||||
Context creation key to specify the maximum number of
|
||||
auxiliary source sends.
|
||||
|
||||
.. data:: HRTF
|
||||
:type: int
|
||||
|
||||
Context creation key to specify whether to enable HRTF
|
||||
(either ``FALSE``, ``TRUE`` or ``DONT_CARE``).
|
||||
|
||||
.. data:: HRTF_ID
|
||||
:type: int
|
||||
|
||||
Context creation key to specify the HRTF to be used.
|
||||
|
||||
.. data:: OUTPUT_LIMITER
|
||||
:type: int
|
||||
|
||||
Context creation key to specify whether to use a gain limiter
|
||||
(either ``FALSE``, ``TRUE`` or ``DONT_CARE``).
|
||||
|
||||
.. data:: distance_models
|
||||
:type: Tuple[str, ...]
|
||||
|
||||
Names of available distance models.
|
|
@ -0,0 +1,44 @@
|
|||
Audio Streams
|
||||
=============
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
Builtin Decoders
|
||||
----------------
|
||||
|
||||
.. autoclass:: Decoder
|
||||
:members:
|
||||
|
||||
Decoder Interface
|
||||
-----------------
|
||||
|
||||
.. data:: decoder_factories
|
||||
:type: DecoderNamespace
|
||||
|
||||
Simple object for storing decoder factories.
|
||||
|
||||
User-registered factories are tried one after another
|
||||
if :py:exc:`RuntimeError` is raised, in lexicographical order.
|
||||
Internal decoder factories are always used after registered ones.
|
||||
|
||||
.. autofunction:: decode
|
||||
|
||||
.. autoclass:: BaseDecoder
|
||||
:members:
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
||||
.. data:: sample_types
|
||||
:type: Tuple[str, ...]
|
||||
|
||||
Names of available sample types.
|
||||
|
||||
.. data:: channel_configs
|
||||
:type: Tuple[str, ...]
|
||||
|
||||
Names of available channel configurations.
|
||||
|
||||
.. autofunction:: sample_size
|
||||
|
||||
.. autofunction:: sample_length
|
|
@ -0,0 +1,21 @@
|
|||
Audio Devices
|
||||
=============
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
Device-Dependent Utilities
|
||||
--------------------------
|
||||
|
||||
.. autoclass:: Device
|
||||
:members:
|
||||
|
||||
Device-Independent Utilities
|
||||
----------------------------
|
||||
|
||||
.. data:: device_names
|
||||
:type: DeviceNames
|
||||
|
||||
Read-only namespace of device names by category (``basic``, ``full`` and
|
||||
``capture``), as tuples of strings whose first item being the default.
|
||||
|
||||
.. autofunction:: query_extension
|
|
@ -0,0 +1,34 @@
|
|||
Environmental Effects
|
||||
=====================
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
For the sake of brevity, we only document the constraints of each effect's
|
||||
properties. Further details can be found at OpenAL's `Effect Extension Guide`_
|
||||
which specifies the purpose and usage of each value.
|
||||
|
||||
Base Effect
|
||||
-----------
|
||||
|
||||
.. autoclass:: BaseEffect
|
||||
:members:
|
||||
|
||||
Chorus Effect
|
||||
-------------
|
||||
|
||||
.. autoclass:: ChorusEffect
|
||||
:members:
|
||||
|
||||
Reverb Effect
|
||||
-------------
|
||||
|
||||
.. data:: reverb_preset_names
|
||||
:type: Tuple[str, ...]
|
||||
|
||||
Names of predefined reverb effect presets in lexicographical order.
|
||||
|
||||
.. autoclass:: ReverbEffect
|
||||
:members:
|
||||
|
||||
.. _Effect Extension Guide:
|
||||
https://kcat.strangesoft.net/misc-downloads/Effects%20Extension%20Guide.pdf
|
|
@ -0,0 +1,11 @@
|
|||
File I/O Interface
|
||||
==================
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
.. autofunction:: current_fileio
|
||||
|
||||
.. autofunction:: use_fileio
|
||||
|
||||
.. autoclass:: FileIO
|
||||
:members:
|
|
@ -0,0 +1,14 @@
|
|||
Reference
|
||||
=========
|
||||
|
||||
API reference is divided into the following sections:
|
||||
|
||||
.. toctree::
|
||||
|
||||
device
|
||||
context
|
||||
buffer
|
||||
source
|
||||
effect
|
||||
decoder
|
||||
file-io
|
|
@ -0,0 +1,16 @@
|
|||
Sources & Source Groups
|
||||
=======================
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
.. autoclass:: Source
|
||||
:members:
|
||||
|
||||
Source Groups
|
||||
-------------
|
||||
|
||||
.. autoclass:: SourceGroup
|
||||
:members:
|
|
@ -0,0 +1,31 @@
|
|||
<h3>Quick Navigation</h3>
|
||||
<ul>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference external" href="https://pypi.org/project/palace/">
|
||||
Python Package Index
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference external"
|
||||
href="https://travis-ci.com/github/McSinyx/palace">
|
||||
Travis CI Build
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference external"
|
||||
href="https://ci.appveyor.com/project/McSinyx/palace">
|
||||
AppVeyor Build
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference external" href="https://github.com/McSinyx/palace">
|
||||
GitHub Repository
|
||||
</a>
|
||||
</li>
|
||||
<li class="toctree-l1">
|
||||
<a class="reference external"
|
||||
href="https://matrix.to/#/#palace-dev:matrix.org">
|
||||
Matrix Chat Room
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
|
@ -0,0 +1,41 @@
|
|||
Context Creation
|
||||
================
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
A context is an object that allows palace to access OpenAL,
|
||||
which is essential when you work with palace. Context maintains
|
||||
the audio environment and contains environment settings and components
|
||||
such as sources, buffers, and effects.
|
||||
|
||||
Creating a Device Object
|
||||
------------------------
|
||||
|
||||
To create a context, we must first create a device,
|
||||
since it's a parameter of the context object.
|
||||
|
||||
To create an object, well, you just have to instantiate
|
||||
the :py:class:`Device` class.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from palace import Device
|
||||
|
||||
with Device() as dev:
|
||||
# Your code goes here
|
||||
|
||||
This is how you declare a :py:class:`Device` object with the default device.
|
||||
There can be several devices available, which can be found
|
||||
in :py:data:`device_names`.
|
||||
|
||||
Creating a Context
|
||||
------------------
|
||||
|
||||
Now that we've created a device, we can create the context:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from palace import Device, Context
|
||||
|
||||
with Device() as dev, Context(dev) as ctx:
|
||||
# Your code goes here
|
|
@ -0,0 +1,65 @@
|
|||
Adding an Effect
|
||||
================
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
This section will focus on how to add effects to the audio.
|
||||
|
||||
There are two set of audio effects supported by palace: :py:class:`ReverbEffect`
|
||||
and :py:class:`ChorusEffect`.
|
||||
|
||||
Reverb Effect
|
||||
-------------
|
||||
|
||||
Reverb happens when a sound is reflected and then decay as the sound is absorbed
|
||||
by the objects in the medium. :py:class:`ReverbEffect` facilitates such effect.
|
||||
|
||||
Creating a reverb effect can be as simple as:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with ReverbEffect() as effect:
|
||||
source.sends[0].effect = effect
|
||||
|
||||
:py:attr:`Source.sends` is a collection of send path signals, each of which
|
||||
contains `effects` and `filter` that describes it. Here we are only concerned
|
||||
about the former.
|
||||
|
||||
The above code would yield a *generic* reverb effect by default.
|
||||
There are several other presets that you can use, which are listed
|
||||
by :py:data:`reverb_preset_names`. To use these preset, you can simply provide
|
||||
the preset effect name as the first parameter for the constructor. For example,
|
||||
to use `PIPE_LARGE` preset effect, you can initialize the effect like below:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with ReverbEffect('PIPE_LARGE') as effect:
|
||||
source.sends[0].effect = effect
|
||||
|
||||
These effects can be modified via their attributes.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
effect.gain = 0.4
|
||||
effect.diffusion = 0.65
|
||||
late_reverb_pan = 0.2, 0.1, 0.3
|
||||
|
||||
The list of these attributes and their constraints can be found
|
||||
in the documentation of :py:class:`ReverbEffect`.
|
||||
|
||||
Chorus Effect
|
||||
-------------
|
||||
|
||||
:py:class:`ChorusEffect` does not have preset effects like
|
||||
:py:class:`ReverbEffect`, so you would have to initialize the effect attributes
|
||||
on creation.
|
||||
|
||||
There are five parameters to initialize the effect, respectively: waveform,
|
||||
phase, depth, feedback, and delay.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with ChorusEffect('sine', 20, 0.4, 0.5, 0.008) as effect:
|
||||
source.sends[0].effect = effect
|
||||
|
||||
For the constraints of these parameters, please refer to the documentation.
|
|
@ -0,0 +1,15 @@
|
|||
Tutorial
|
||||
========
|
||||
|
||||
This tutorial will guide you on:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
context
|
||||
play-audio
|
||||
source
|
||||
effect
|
||||
.. comment these to add later
|
||||
Customize decoder
|
||||
Generate sounds
|
|
@ -0,0 +1,101 @@
|
|||
Play an Audio
|
||||
=============
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
Now that you know how to create a context,
|
||||
let's get into the most essential use case: playing audio.
|
||||
|
||||
Creating a Source
|
||||
-----------------
|
||||
|
||||
To play an audio, you have to create a source. This source
|
||||
is an imaginary sound broadcaster, whose positions and properties
|
||||
can be changed to create desired effects.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from palace import Device, Context, Source
|
||||
|
||||
with Device() as dev, Context(dev) as ctx:
|
||||
with Source() as src:
|
||||
# to be written
|
||||
|
||||
Just like for the case of :py:class:`Context`, :py:class:`Source` creation
|
||||
requires a context, but here the context is passed implicitly.
|
||||
|
||||
Decode the Audio File
|
||||
---------------------
|
||||
|
||||
Palace has a module level function :py:func:`decode`, which decodes audio file
|
||||
automatically, and this decoded file is a :py:class:`Decoder` object. This object
|
||||
can be played by a simple :py:meth:`Decoder.play` method.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from palace import Device, Context, Source, decode
|
||||
|
||||
filename = 'some_audio.ogg'
|
||||
with Device() as dev, Context(dev) as ctx:
|
||||
with Source() as src:
|
||||
dec = decode(filename)
|
||||
|
||||
We are almost there. Now, let's look at the document for :py:meth:`Decoder.play`.
|
||||
The method takes 3 parameters: ``chunk_len``, ``queue_size``, and ``source``.
|
||||
|
||||
The source object is optional, because if you don't have it, a new source
|
||||
will be generated by default.
|
||||
|
||||
The audio is divided into chunks, each of which is of length ``chunk_len``.
|
||||
Then ``queue_size`` is the number of these chunks that it will play.
|
||||
|
||||
.. TODO: I think it's better to include a diagram here. Add later
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from palace import Device, Context, Source, decode
|
||||
|
||||
filename = 'some_audio.ogg'
|
||||
with Device() as dev, Context(dev) as ctx:
|
||||
with Source() as src:
|
||||
dec = decode(filename)
|
||||
dec.play(12000, 4, src)
|
||||
|
||||
But we don't want it to play only a small part of the audio. We want it to
|
||||
play all of it. How do we do that? The answer is a loop.
|
||||
|
||||
There is a method, :py:meth:`Context.update`, which update the context and the source.
|
||||
When the source is updated, it will be filled with new chunks of data from
|
||||
the decoder.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from palace import Device, Context, Source, decode
|
||||
|
||||
filename = 'some_audio.ogg'
|
||||
with Device() as dev, Context(dev) as ctx:
|
||||
with Source() as src:
|
||||
dec = decode(filename)
|
||||
dec.play(12000, 4, src)
|
||||
while src.playing:
|
||||
ctx.update()
|
||||
|
||||
If you tried this code for a song, you will find that it's a bit rush.
|
||||
That is because the source is renewed too fast. So, a simple solution
|
||||
is to ``sleep`` for a while.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from time import sleep
|
||||
from palace import Device, Context, Source, decode
|
||||
|
||||
filename = 'some_audio.ogg'
|
||||
with Device() as dev, Context(dev) as ctx:
|
||||
with Source() as src:
|
||||
dec = decode(filename)
|
||||
dec.play(12000, 4, src)
|
||||
while src.playing:
|
||||
sleep(0.025)
|
||||
ctx.update()
|
||||
|
||||
Congratulation! Enjoy your music before we get to the next part of this tutorial.
|
|
@ -0,0 +1,82 @@
|
|||
Source Manipulation
|
||||
===================
|
||||
|
||||
.. currentmodule:: palace
|
||||
|
||||
We have created a source in the last section.
|
||||
As said previously, its properties can be manipulated to create wanted effects.
|
||||
|
||||
Moving the Source
|
||||
-----------------
|
||||
|
||||
Changing :py:attr:`Source.position` is one of the most noticeable,
|
||||
but first, we have to enable spatialization via :py:attr:`Source.spatialize`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from time import sleep
|
||||
from palace import Device, Context, Source, decode
|
||||
|
||||
with Device() as device, Context(device) as context, Source() as source:
|
||||
source.spatialize = True
|
||||
decoder = decode('some_audio.ogg')
|
||||
decoder.play(12000, 4, source)
|
||||
while source.playing:
|
||||
sleep(0.025)
|
||||
context.update()
|
||||
|
||||
Now, we can set the position of the source in this virtual 3D space.
|
||||
The position is a 3-tuple indicating the coordinate of the source.
|
||||
The axes are aligned according to the normal coordinate system:
|
||||
|
||||
- The x-axis goes from left to right
|
||||
- The y-axis goes from below to above
|
||||
- The z-axis goes from front to back
|
||||
|
||||
For example, this will set the source above the listener::
|
||||
|
||||
src.position = 0, 1, 0
|
||||
|
||||
.. note::
|
||||
|
||||
For this too work for stereo, you have to have HRTF enabled.
|
||||
You can check that via :py:attr:`Device.current_hrtf`.
|
||||
|
||||
You can as well use a function to move the source automatically by writing
|
||||
a function that generate positions. A simple example is circular motion.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from itertools import takewhile, count
|
||||
...
|
||||
for i in takewhile(src.playing, count(step=0.025)):
|
||||
source.position = sin(i), 0, cos(-i)
|
||||
...
|
||||
|
||||
A more well-written example of this can be found `in our repository`_.
|
||||
|
||||
Speed and Pitch
|
||||
---------------
|
||||
|
||||
Modifying :py:attr:`pitch` changes the playing speed, effectively changing
|
||||
pitch. Pitch can be any positive number.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
src.pitch = 2 # high pitch
|
||||
src.pitch = 0.4 # low pitch
|
||||
|
||||
Air Absorption Factor
|
||||
---------------------
|
||||
|
||||
:py:attr:`Source.air_absorption_factor` simulates atmospheric high-frequency
|
||||
air absorption. Higher values simulate foggy air and lower values simulate
|
||||
drier air.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
src.air_absorption_factor = 9 # very high humidity
|
||||
src.air_absorption_factor = 0 # dry air (default)
|
||||
|
||||
.. _in our repository:
|
||||
https://github.com/McSinyx/palace/blob/master/examples/palace-hrtf.py
|
|
@ -26,7 +26,7 @@ from palace import Context, Device, Source, decode
|
|||
|
||||
CHUNK_LEN: int = 12000
|
||||
QUEUE_SIZE: int = 4
|
||||
PERIOD: float = 0.01
|
||||
PERIOD: float = 0.025
|
||||
|
||||
|
||||
def play(files: Iterable[str], device: str) -> None:
|
||||
|
|
|
@ -23,7 +23,8 @@ from sys import stderr
|
|||
from time import sleep
|
||||
from typing import Iterable
|
||||
|
||||
from palace import reverb_preset_names, decode, Device, Context, Source, Effect
|
||||
from palace import (reverb_preset_names, decode,
|
||||
Device, Context, Source, ReverbEffect)
|
||||
|
||||
CHUNK_LEN: int = 12000
|
||||
QUEUE_SIZE: int = 4
|
||||
|
@ -48,11 +49,9 @@ def play(files: Iterable[str], device: str, reverb: str) -> None:
|
|||
"""Load and play files on the given device."""
|
||||
with Device(device) as dev, Context(dev) as ctx:
|
||||
print('Opened', dev.name)
|
||||
with Source() as src, Effect() as fx:
|
||||
print('Loading reverb preset', reverb)
|
||||
fx.reverb_preset = reverb
|
||||
print('Loading reverb preset', reverb)
|
||||
with Source() as src, ReverbEffect(reverb) as fx:
|
||||
src.sends[0].effect = fx
|
||||
|
||||
for filename in files:
|
||||
try:
|
||||
decoder = decode(filename)
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[build-system]
|
||||
requires = ['setuptools>=43', 'wheel>=0.31', 'Cython']
|
||||
build-backend = "setuptools.build_meta"
|
||||
build-backend = 'setuptools.build_meta'
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
[metadata]
|
||||
name = palace
|
||||
version = 0.1.3
|
||||
url = https://github.com/McSinyx/palace
|
||||
version = 0.2.2
|
||||
url = https://mcsinyx.github.io/palace
|
||||
author = Nguyễn Gia Phong
|
||||
author_email = mcsinyx@disroot.org
|
||||
classifiers =
|
||||
Development Status :: 3 - Alpha
|
||||
Development Status :: 4 - Beta
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
|
||||
Operating System :: MacOS
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: C++
|
||||
Programming Language :: Cython
|
||||
|
@ -19,7 +20,7 @@ classifiers =
|
|||
Topic :: Software Development :: Libraries
|
||||
Typing :: Typed
|
||||
license = LGPLv3+
|
||||
license_file = LICENSE
|
||||
license_files = LICENSE
|
||||
description = Pythonic Audio Library and Codecs Environment
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
|
|
18
setup.py
18
setup.py
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
# setup script
|
||||
# Copyright (C) 2019, 2020 Nguyễn Gia Phong
|
||||
# Copyright (C) 2020 Francesco Caliumi
|
||||
#
|
||||
# This file is part of palace.
|
||||
#
|
||||
|
@ -21,13 +22,13 @@ import re
|
|||
from distutils import log
|
||||
from distutils.command.clean import clean
|
||||
from distutils.dir_util import mkpath
|
||||
from distutils.errors import DistutilsFileError
|
||||
from distutils.errors import DistutilsExecError, DistutilsFileError
|
||||
from distutils.file_util import copy_file
|
||||
from operator import methodcaller
|
||||
from os import environ, unlink
|
||||
from os.path import dirname, join
|
||||
from platform import system
|
||||
from subprocess import DEVNULL, PIPE, run
|
||||
from subprocess import DEVNULL, PIPE, run, CalledProcessError
|
||||
|
||||
from Cython.Build import cythonize
|
||||
from setuptools import setup, Extension
|
||||
|
@ -55,10 +56,15 @@ class BuildAlure2Ext(build_ext):
|
|||
"""
|
||||
super().finalize_options()
|
||||
mkpath(self.build_temp)
|
||||
copy_file(join(dirname(__file__), 'CMakeLists.txt'),
|
||||
self.build_temp)
|
||||
cmake = run(['cmake', '.'], check=True, stdout=DEVNULL, stderr=PIPE,
|
||||
cwd=self.build_temp, universal_newlines=True)
|
||||
copy_file(join(dirname(__file__), 'CMakeLists.txt'), self.build_temp)
|
||||
try:
|
||||
cmake = run(
|
||||
['cmake', '.'], check=True, stdout=DEVNULL, stderr=PIPE,
|
||||
cwd=self.build_temp, universal_newlines=True)
|
||||
except CalledProcessError as e:
|
||||
log.error(e.stderr.strip())
|
||||
raise DistutilsExecError(str(e))
|
||||
|
||||
for key, value in map(methodcaller('groups'),
|
||||
re.finditer(r'^alure2_(\w*)=(.*)$',
|
||||
cmake.stderr, re.MULTILINE)):
|
||||
|
|
228
src/alure.pxd
228
src/alure.pxd
|
@ -25,7 +25,7 @@ from libcpp.string cimport string
|
|||
from libcpp.utility cimport pair
|
||||
from libcpp.vector cimport vector
|
||||
|
||||
from std cimport duration, nanoseconds, milliseconds, shared_future, streambuf
|
||||
from std cimport duration, nanoseconds, milliseconds, streambuf
|
||||
|
||||
|
||||
# OpenAL and Alure auxiliary declarations
|
||||
|
@ -84,37 +84,37 @@ cdef extern from 'alure2-typeviews.h' namespace 'alure' nogil:
|
|||
# Alure main module
|
||||
cdef extern from 'alure2.h' nogil:
|
||||
cdef cppclass EFXEAXREVERBPROPERTIES:
|
||||
float flDensity
|
||||
float flDiffusion
|
||||
float flGain
|
||||
float flGainHF
|
||||
float flGainLF
|
||||
float flDecayTime
|
||||
float flDecayHFRatio
|
||||
float flDecayLFRatio
|
||||
float flReflectionsGain
|
||||
float flReflectionsDelay
|
||||
float flReflectionsPan[3]
|
||||
float flLateReverbGain
|
||||
float flLateReverbDelay
|
||||
float flLateReverbPan[3]
|
||||
float flEchoTime
|
||||
float flEchoDepth
|
||||
float flModulationTime
|
||||
float flModulationDepth
|
||||
float flAirAbsorptionGainHF
|
||||
float flHFReference
|
||||
float flLFReference
|
||||
float flRoomRolloffFactor
|
||||
int iDecayHFLimit
|
||||
float density 'flDensity'
|
||||
float diffusion 'flDiffusion'
|
||||
float gain 'flGain'
|
||||
float gain_hf 'flGainHF'
|
||||
float gain_lf 'flGainLF'
|
||||
float decay_time 'flDecayTime'
|
||||
float decay_hf_ratio 'flDecayHFRatio'
|
||||
float decay_lf_ratio 'flDecayLFRatio'
|
||||
float reflections_gain 'flReflectionsGain'
|
||||
float reflections_delay 'flReflectionsDelay'
|
||||
float reflections_pan 'flReflectionsPan'[3]
|
||||
float late_reverb_gain 'flLateReverbGain'
|
||||
float late_reverb_delay 'flLateReverbDelay'
|
||||
float late_reverb_pan 'flLateReverbPan'[3]
|
||||
float echo_time 'flEchoTime'
|
||||
float echo_depth 'flEchoDepth'
|
||||
float modulation_time 'flModulationTime'
|
||||
float modulation_depth 'flModulationDepth'
|
||||
float air_absorption_gain_hf 'flAirAbsorptionGainHF'
|
||||
float hf_reference 'flHFReference'
|
||||
float lf_reference 'flLFReference'
|
||||
float room_rolloff_factor 'flRoomRolloffFactor'
|
||||
bint decay_hf_limit 'iDecayHFLimit'
|
||||
|
||||
cdef cppclass EFXCHORUSPROPERTIES:
|
||||
int iWaveform
|
||||
int iPhase
|
||||
float flRate
|
||||
float flDepth
|
||||
float flFeedback
|
||||
float flDelay
|
||||
bint waveform 'iWaveform'
|
||||
int phase 'iPhase'
|
||||
float rate 'flRate'
|
||||
float depth 'flDepth'
|
||||
float feedback 'flFeedback'
|
||||
float delay 'flDelay'
|
||||
|
||||
|
||||
cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||
|
@ -129,7 +129,6 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
# String: string
|
||||
# StringView: string
|
||||
# SharedPtr: shared_ptr
|
||||
# SharedFuture: shared_future
|
||||
|
||||
# Structs:
|
||||
cdef cppclass AttributePair:
|
||||
|
@ -142,9 +141,9 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
unsigned send 'mSend'
|
||||
|
||||
# Enum classes:
|
||||
cdef enum SampleType:
|
||||
ctypedef enum SampleType:
|
||||
pass
|
||||
cdef enum ChannelConfig:
|
||||
ctypedef enum ChannelConfig:
|
||||
pass
|
||||
|
||||
# The following relies on C++ implicit conversion from char* to string.
|
||||
|
@ -153,24 +152,24 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
cdef unsigned frames_to_bytes 'FramesToBytes'(unsigned, ChannelConfig, SampleType) except +
|
||||
cdef unsigned bytes_to_frames 'BytesToFrames'(unsigned, ChannelConfig, SampleType)
|
||||
|
||||
cdef enum DeviceEnumeration:
|
||||
ctypedef enum DeviceEnumeration:
|
||||
Basic 'alure::DeviceEnumeration::Basic'
|
||||
Full 'alure::DeviceEnumeration::Full'
|
||||
Capture 'alure::DeviceEnumeration::Capture'
|
||||
|
||||
cdef enum DefaultDeviceType:
|
||||
ctypedef enum DefaultDeviceType:
|
||||
Basic 'alure::DefaultDeviceType::Basic'
|
||||
Full 'alure::DefaultDeviceType::Full'
|
||||
Capture 'alure::DefaultDeviceType::Capture'
|
||||
|
||||
cdef enum PlaybackName:
|
||||
ctypedef enum PlaybackName:
|
||||
Basic 'alure::PlaybackName::Basic'
|
||||
Full 'alure::PlaybackName::Full'
|
||||
|
||||
cdef enum DistanceModel:
|
||||
ctypedef enum DistanceModel:
|
||||
pass
|
||||
|
||||
cdef enum Spatialize:
|
||||
ctypedef enum Spatialize:
|
||||
Off 'alure::Spatialize::Off'
|
||||
On 'alure::Spatialize::On'
|
||||
Auto 'alure::Spatialize::Auto'
|
||||
|
@ -209,48 +208,28 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
cdef cppclass DeviceManager:
|
||||
@staticmethod
|
||||
DeviceManager get_instance 'getInstance'() except +
|
||||
|
||||
DeviceManager() # nil
|
||||
DeviceManager(const DeviceManager&)
|
||||
DeviceManager(DeviceManager&&)
|
||||
|
||||
boolean operator bool()
|
||||
|
||||
boolean query_extension 'queryExtension'(const string&) except +
|
||||
|
||||
vector[string] enumerate(DeviceEnumeration) except +
|
||||
string default_device_name 'defaultDeviceName'(DefaultDeviceType) except +
|
||||
|
||||
Device open_playback 'openPlayback'() except +
|
||||
Device open_playback 'openPlayback'(const string&) except +
|
||||
|
||||
cdef cppclass Device:
|
||||
ctypedef DeviceImpl* handle_type
|
||||
|
||||
Device() # nil
|
||||
Device(DeviceImpl*)
|
||||
Device(const Device&)
|
||||
Device(Device&&)
|
||||
|
||||
Device& operator=(const Device&)
|
||||
Device& operator=(Device&&)
|
||||
|
||||
boolean operator==(const Device&)
|
||||
boolean operator!=(const Device&)
|
||||
boolean operator<=(const Device&)
|
||||
boolean operator>=(const Device&)
|
||||
boolean operator<(const Device&)
|
||||
boolean operator>(const Device&)
|
||||
|
||||
boolean operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
|
||||
string get_name 'getName'() except +
|
||||
string get_name 'getName'(PlaybackName) except +
|
||||
|
||||
boolean query_extension 'queryExtension'(const string&) except +
|
||||
|
||||
Version get_alc_version 'getALCVersion'() except +
|
||||
Version get_efx_version 'getEFXVersion'() except +
|
||||
|
||||
|
@ -274,34 +253,24 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
void close() except +
|
||||
|
||||
cdef cppclass Context:
|
||||
ctypedef ContextImpl* handle_type
|
||||
|
||||
Context() # nil
|
||||
Context(ContextImpl*)
|
||||
Context(const Context&)
|
||||
Context(Context&&)
|
||||
|
||||
Context& operator=(const Context&)
|
||||
Context& operator=(Context&&)
|
||||
|
||||
boolean operator==(const Context&)
|
||||
boolean operator!=(const Context&)
|
||||
boolean operator<=(const Context&)
|
||||
boolean operator>=(const Context&)
|
||||
boolean operator<(const Context&)
|
||||
boolean operator>(const Context&)
|
||||
|
||||
boolean operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
|
||||
@staticmethod
|
||||
void make_current 'MakeCurrent'(Context) except +
|
||||
|
||||
@staticmethod
|
||||
Context get_current 'GetCurrent'() except +
|
||||
|
||||
@staticmethod
|
||||
void make_thread_current 'MakeThreadCurrent'(Context) except +
|
||||
|
||||
@staticmethod
|
||||
Context get_thread_current 'GetThreadCurrent'() except +
|
||||
|
||||
|
@ -328,13 +297,8 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
int get_default_resampler_index 'getDefaultResamplerIndex'() except +
|
||||
|
||||
void precache_buffers_async 'precacheBuffersAsync'(vector[StringView]) except +
|
||||
|
||||
Buffer create_buffer_from 'createBufferFrom'(string, shared_ptr[Decoder]) except +
|
||||
shared_future[Buffer] create_buffer_async_from 'createBufferAsyncFrom'(string, shared_ptr[Decoder]) except +
|
||||
|
||||
Buffer find_buffer 'findBuffer'(string) except +
|
||||
shared_future[Buffer] find_buffer_async 'findBufferAsync'(string) except +
|
||||
|
||||
void remove_buffer 'removeBuffer'(string) except +
|
||||
void remove_buffer 'removeBuffer'(Buffer) except +
|
||||
|
||||
|
@ -350,63 +314,25 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
void update() except +
|
||||
|
||||
cdef cppclass Listener:
|
||||
ctypedef ListenerImpl* handle_type
|
||||
|
||||
Listener() # nil
|
||||
Listener(ListenerImpl*)
|
||||
Listener(const Listener&)
|
||||
Listener(Listener&&)
|
||||
|
||||
Listener& operator=(const Listener&)
|
||||
Listener& operator=(Listener&&)
|
||||
|
||||
boolean operator==(const Listener&)
|
||||
boolean operator!=(const Listener&)
|
||||
boolean operator<=(const Listener&)
|
||||
boolean operator>=(const Listener&)
|
||||
boolean operator<(const Listener&)
|
||||
boolean operator>(const Listener&)
|
||||
|
||||
boolean operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
|
||||
float set_gain 'setGain'(float) except +
|
||||
float set_3d_parameters 'set3DParameters'(const Vector3&, const Vector3&, const Vector3&) except +
|
||||
void set_position 'setPosition'(const Vector3 &) except +
|
||||
void set_position 'setPosition'(const float*) except +
|
||||
|
||||
void set_velocity 'setVelocity'(const Vector3&) except +
|
||||
void set_velocity 'setVelocity'(const float*) except +
|
||||
|
||||
void set_orientation 'setOrientation'(const pair[Vector3, Vector3]&) except +
|
||||
void set_orientation 'setOrientation'(const float*, const float*) except +
|
||||
void set_orientation 'setOrientation'(const float*) except +
|
||||
|
||||
void set_meters_per_unit 'setMetersPerUnit'(float) except +
|
||||
|
||||
cdef cppclass Buffer:
|
||||
ctypedef BufferImpl* handle_type
|
||||
|
||||
Buffer() # nil
|
||||
Buffer(BufferImpl*)
|
||||
Buffer(const Buffer&)
|
||||
Buffer(Buffer&&)
|
||||
|
||||
Buffer& operator=(const Buffer&)
|
||||
Buffer& operator=(Buffer&&)
|
||||
|
||||
boolean operator==(const Buffer&)
|
||||
boolean operator!=(const Buffer&)
|
||||
boolean operator<=(const Buffer&)
|
||||
boolean operator>=(const Buffer&)
|
||||
boolean operator<(const Buffer&)
|
||||
boolean operator>(const Buffer&)
|
||||
|
||||
boolean operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
|
||||
unsigned get_length 'getLength'() except +
|
||||
unsigned get_frequency 'getFrequency'() except +
|
||||
ChannelConfig get_channel_config 'getChannelConfig'() except +
|
||||
|
@ -414,45 +340,29 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
unsigned get_size 'getSize'() except +
|
||||
size_t get_source_count 'getSourceCount'() except +
|
||||
vector[Source] get_sources 'getSources'() except +
|
||||
# name is implemented as a read-only attribute in Cython
|
||||
pair[unsigned, unsigned] get_loop_points 'getLoopPoints'() except +
|
||||
void set_loop_points 'setLoopPoints'(unsigned, unsigned) except +
|
||||
|
||||
cdef cppclass Source:
|
||||
ctypedef SourceImpl* handle_type
|
||||
|
||||
Source() # nil
|
||||
Source(SourceImpl*)
|
||||
Source(const Source&)
|
||||
Source(Source&&)
|
||||
|
||||
Source& operator=(const Source&)
|
||||
Source& operator=(Source&&)
|
||||
|
||||
boolean operator==(const Source&)
|
||||
boolean operator!=(const Source&)
|
||||
boolean operator<=(const Source&)
|
||||
boolean operator>=(const Source&)
|
||||
boolean operator<(const Source&)
|
||||
boolean operator>(const Source&)
|
||||
|
||||
boolean operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
|
||||
void play(Buffer) except +
|
||||
void play(shared_ptr[Decoder], int, int) except +
|
||||
void play(shared_future[Buffer]) except +
|
||||
|
||||
void stop() except +
|
||||
void fade_out_to_stop 'fadeOutToStop'(float, milliseconds) except +
|
||||
void pause() except +
|
||||
void resume() except +
|
||||
|
||||
boolean is_pending 'isPending'() except +
|
||||
boolean is_playing 'isPlaying'() except +
|
||||
boolean is_paused 'isPaused'() except +
|
||||
boolean is_playing_or_pending 'isPlayingOrPending'() except +
|
||||
|
||||
void set_group 'setGroup'(SourceGroup) except +
|
||||
SourceGroup get_group 'getGroup'() except +
|
||||
|
@ -476,50 +386,30 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
float get_gain 'getGain'() except +
|
||||
void set_gain_range 'setGainRange'(float, float) except +
|
||||
pair[float, float] get_gain_range 'getGainRange'() except +
|
||||
float get_min_gain 'getMinGain'() except +
|
||||
float get_max_gain 'getMaxGain'() except +
|
||||
|
||||
void set_distance_range 'setDistanceRange'(float, float) except +
|
||||
pair[float, float] get_distance_range 'getDistanceRange'() except +
|
||||
float get_reference_distance 'getReferenceDistance'() except +
|
||||
float get_max_distance 'getMaxDistance'() except +
|
||||
|
||||
void set_3d_parameters 'set3DParameters'(const Vector3&, const Vector3&, const Vector3&) except +
|
||||
void set_3d_parameters 'set3DParameters'(const Vector3&, const Vector3&, const pair[Vector3, Vector3]&) except +
|
||||
|
||||
void set_position 'setPosition'(const Vector3&) except +
|
||||
void set_position 'setPosition'(const float*) except +
|
||||
Vector3 get_position 'getPosition'() except +
|
||||
|
||||
void set_velocity 'setVelocity'(const Vector3&) except +
|
||||
void set_velocity 'setVelocity'(const float*) except +
|
||||
Vector3 get_velocity 'getVelocity'() except +
|
||||
|
||||
void set_direction 'setDirection'(const Vector3&) except +
|
||||
void set_direction 'setDirection'(const float*) except +
|
||||
Vector3 get_direction 'getDirection'() except +
|
||||
|
||||
void set_orientation 'setOrientation'(const pair[Vector3, Vector3]&) except +
|
||||
void set_orientation 'setOrientation'(const float*, const float*) except +
|
||||
void set_orientation 'setOrientation'(const float*) except +
|
||||
pair[Vector3, Vector3] get_orientation 'getOrientation'() except +
|
||||
|
||||
void set_cone_angles 'setConeAngles'(float, float) except +
|
||||
pair[float, float] get_cone_angles 'getConeAngles'() except +
|
||||
float get_inner_cone_angle 'getInnerConeAngle'() except +
|
||||
float get_outer_cone_angle 'getOuterConeAngle'() except +
|
||||
|
||||
void set_outer_cone_gains 'setOuterConeGains'(float) except +
|
||||
void set_outer_cone_gains 'setOuterConeGains'(float, float) except +
|
||||
pair[float, float] get_outer_cone_gains 'getOuterConeGains'() except +
|
||||
float get_outer_cone_gain 'getOuterConeGain'() except +
|
||||
float get_outer_cone_gainhf 'getOuterConeGainHF'() except +
|
||||
|
||||
void set_rolloff_factors 'setRolloffFactors'(float) except +
|
||||
void set_rolloff_factors 'setRolloffFactors'(float, float) except +
|
||||
pair[float, float] get_rolloff_factors 'getRolloffFactors'() except +
|
||||
float get_rolloff_factor 'getRolloffFactor'() except +
|
||||
float get_room_rolloff_factor 'getRoomRolloffFactor'() except +
|
||||
|
||||
void set_doppler_factor 'setDopplerFactor'(float) except +
|
||||
float get_doppler_factor 'getDopplerFactor'() except +
|
||||
|
@ -556,26 +446,15 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
void destroy() except +
|
||||
|
||||
cdef cppclass SourceGroup:
|
||||
ctypedef SourceImpl* handle_type
|
||||
|
||||
SourceGroup() # nil
|
||||
SourceGroup(SourceGroupImpl*)
|
||||
SourceGroup(const SourceGroup&)
|
||||
SourceGroup(SourceGroup&&)
|
||||
|
||||
SourceGroup& operator=(const SourceGroup&)
|
||||
SourceGroup& operator=(SourceGroup&&)
|
||||
|
||||
boolean operator==(const SourceGroup&)
|
||||
boolean operator!=(const SourceGroup&)
|
||||
boolean operator<=(const SourceGroup&)
|
||||
boolean operator>=(const SourceGroup&)
|
||||
boolean operator<(const SourceGroup&)
|
||||
boolean operator>(const SourceGroup&)
|
||||
|
||||
boolean operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
void set_parent_group 'setParentGroup'(SourceGroup) except +
|
||||
SourceGroup get_parent_group 'getParentGroup'() except +
|
||||
|
||||
|
@ -595,27 +474,15 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
void destroy() except +
|
||||
|
||||
cdef cppclass AuxiliaryEffectSlot:
|
||||
ctypedef AuxiliaryEffectSlotImpl* handle_type
|
||||
|
||||
AuxiliaryEffectSlot() # nil
|
||||
AuxiliaryEffectSlot(AuxiliaryEffectSlotImpl*)
|
||||
AuxiliaryEffectSlot(const AuxiliaryEffectSlot&)
|
||||
AuxiliaryEffectSlot(AuxiliaryEffectSlot&&)
|
||||
|
||||
AuxiliaryEffectSlot& operator=(const AuxiliaryEffectSlot&)
|
||||
AuxiliaryEffectSlot& operator=(AuxiliaryEffectSlot&&)
|
||||
|
||||
boolean operator==(const AuxiliaryEffectSlot&)
|
||||
boolean operator!=(const AuxiliaryEffectSlot&)
|
||||
boolean operator<=(const AuxiliaryEffectSlot&)
|
||||
boolean operator>=(const AuxiliaryEffectSlot&)
|
||||
boolean operator<(const AuxiliaryEffectSlot&)
|
||||
boolean operator>(const AuxiliaryEffectSlot&)
|
||||
|
||||
boolean operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
|
||||
void set_gain 'setGain'(float) except +
|
||||
void set_send_auto 'setSendAuto'(bool) except +
|
||||
void apply_effect 'applyEffect'(Effect) except +
|
||||
|
@ -625,42 +492,26 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
size_t get_use_count 'getUseCount'() except +
|
||||
|
||||
cdef cppclass Effect:
|
||||
ctypedef EffectImpl* handle_type
|
||||
|
||||
Effect() # nil
|
||||
Effect(EffectImpl*)
|
||||
Effect(const Effect&)
|
||||
Effect(Effect&&)
|
||||
|
||||
Effect& operator=(const Effect&)
|
||||
Effect& operator=(Effect&&)
|
||||
|
||||
boolean operator==(const Effect&)
|
||||
boolean operator!=(const Effect&)
|
||||
boolean operator<=(const Effect&)
|
||||
boolean operator>=(const Effect&)
|
||||
boolean operator<(const Effect&)
|
||||
boolean operator>(const Effect&)
|
||||
|
||||
boolean operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
|
||||
void set_reverb_properties 'setReverbProperties'(const EFXEAXREVERBPROPERTIES&) except +
|
||||
void set_chorus_properties 'setChorusProperties'(const EFXCHORUSPROPERTIES&) except +
|
||||
|
||||
void destroy() except +
|
||||
|
||||
cdef cppclass Decoder:
|
||||
int get_frequency 'getFrequency'()
|
||||
ChannelConfig get_channel_config 'getChannelConfig'()
|
||||
SampleType get_sample_type 'getSampleType'()
|
||||
|
||||
uint64_t get_length 'getLength'()
|
||||
boolean seek(uint64_t)
|
||||
|
||||
pair[uint64_t, uint64_t] get_loop_points 'getLoopPoints'()
|
||||
|
||||
int read(void*, int)
|
||||
|
||||
cdef cppclass DecoderFactory:
|
||||
|
@ -669,6 +520,7 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
|||
cdef cppclass FileIOFactory:
|
||||
@staticmethod
|
||||
unique_ptr[FileIOFactory] set(unique_ptr[FileIOFactory])
|
||||
|
||||
@staticmethod
|
||||
FileIOFactory& get()
|
||||
|
||||
|
|
1031
src/palace.pyx
1031
src/palace.pyx
File diff suppressed because it is too large
Load Diff
|
@ -37,12 +37,6 @@ cdef extern from '<iostream>' namespace 'std' nogil:
|
|||
istream(streambuf*) except +
|
||||
|
||||
|
||||
cdef extern from '<future>' namespace 'std' nogil:
|
||||
cdef cppclass shared_future[R]:
|
||||
R& get() except +
|
||||
boolean valid() const
|
||||
|
||||
|
||||
cdef extern from '<ratio>' namespace 'std' nogil:
|
||||
cdef cppclass nano:
|
||||
pass
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# test environment
|
||||
# Common test fixtures
|
||||
# Copyright (C) 2020 Ngô Ngọc Đức Huy
|
||||
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of palace.
|
||||
|
@ -16,23 +17,38 @@
|
|||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with palace. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""This module provide default objects of palace classes as fixtures
|
||||
for convenient testing.
|
||||
"""
|
||||
from os.path import abspath, dirname, join
|
||||
|
||||
from pytest import fixture
|
||||
from palace import Device, Context
|
||||
|
||||
DATA_DIR = abspath(join(dirname(__file__), 'data'))
|
||||
|
||||
|
||||
@fixture(scope='session')
|
||||
def device():
|
||||
"""Provide the default device."""
|
||||
with Device() as dev: yield dev
|
||||
@fixture
|
||||
def aiff():
|
||||
"""Provide a sample AIFF file."""
|
||||
return join(DATA_DIR, '24741__tim-kahn__b23-c1-raw.aiff')
|
||||
|
||||
|
||||
@fixture(scope='session')
|
||||
def context(device):
|
||||
"""Provide a context creared from the default device
|
||||
(default context).
|
||||
"""
|
||||
with Context(device) as ctx: yield ctx
|
||||
@fixture
|
||||
def flac():
|
||||
"""Provide a sample FLAC file."""
|
||||
return join(DATA_DIR, '261590__kwahmah-02__little-glitch.flac')
|
||||
|
||||
|
||||
@fixture
|
||||
def mp3():
|
||||
"""Provide a sample MP3 file."""
|
||||
return join(DATA_DIR, '353684__tec-studio__drip2.mp3')
|
||||
|
||||
|
||||
@fixture
|
||||
def ogg():
|
||||
"""Provide a sample Ogg Vorbis file."""
|
||||
return join(DATA_DIR, '164957__zonkmachine__white-noise.ogg')
|
||||
|
||||
|
||||
@fixture
|
||||
def wav():
|
||||
"""Provide a sample WAVE file."""
|
||||
return join(DATA_DIR, '99642__jobro__deconvoluted-20hz-to-20khz.wav')
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,81 @@
|
|||
# Context managers' functional tests
|
||||
# Copyright (C) 2020 Ngô Ngọc Đức Huy
|
||||
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of palace.
|
||||
#
|
||||
# palace is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
#
|
||||
# palace is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with palace. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from palace import (current_context, cache, free, decode, Device, Context,
|
||||
Buffer, Source, SourceGroup, ReverbEffect, ChorusEffect)
|
||||
from pytest import mark, raises
|
||||
|
||||
|
||||
def test_current_context():
|
||||
"""Test the current context."""
|
||||
with Device() as device, Context(device) as context:
|
||||
assert current_context() == context
|
||||
assert current_context() is None
|
||||
|
||||
|
||||
def test_stream_loading(wav):
|
||||
"""Test implication of context during stream loading."""
|
||||
with Device() as device, Context(device): decode(wav)
|
||||
with raises(RuntimeError): decode(wav)
|
||||
|
||||
|
||||
@mark.skip(reason='deadlock (GH-73)')
|
||||
def test_cache_and_free(aiff, flac, ogg):
|
||||
"""Test cache and free, with and without a current context."""
|
||||
with Device() as device, Context(device):
|
||||
cache([aiff, flac, ogg])
|
||||
free([aiff, flac, ogg])
|
||||
with raises(RuntimeError): cache([aiff, flac, ogg])
|
||||
with raises(RuntimeError): free([aiff, flac, ogg])
|
||||
|
||||
|
||||
def test_buffer_loading(mp3):
|
||||
"""Test implication of context during buffer loading."""
|
||||
with Device() as device, Context(device):
|
||||
with Buffer(mp3): pass
|
||||
with raises(RuntimeError):
|
||||
with Buffer(mp3): pass
|
||||
|
||||
|
||||
@mark.parametrize('cls', [Source, SourceGroup, ReverbEffect, ChorusEffect])
|
||||
def test_init_others(cls):
|
||||
"""Test implication of context during object initialization."""
|
||||
with Device() as device, Context(device):
|
||||
with cls(): pass
|
||||
with raises(RuntimeError):
|
||||
with cls(): pass
|
||||
|
||||
|
||||
def test_nested_context_manager():
|
||||
"""Test if the context manager returns to the previous context."""
|
||||
with Device() as device, Context(device) as context:
|
||||
with Context(device): pass
|
||||
assert current_context() == context
|
||||
|
||||
|
||||
@mark.parametrize('data', [
|
||||
'air_absorption_factor', 'cone_angles', 'distance_range', 'doppler_factor',
|
||||
'gain', 'gain_auto', 'gain_range', 'group', 'looping', 'offset',
|
||||
'orientation', 'outer_cone_gains', 'pitch', 'position', 'radius',
|
||||
'relative', 'rolloff_factors', 'spatialize', 'stereo_angles', 'velocity'])
|
||||
def test_source_setter(data):
|
||||
"""Test setters of a Source when its context is not current."""
|
||||
with Device() as device, Context(device), Source() as source:
|
||||
with raises(RuntimeError), Context(device):
|
||||
setattr(source, data, getattr(source, data))
|
|
@ -0,0 +1,113 @@
|
|||
# Functional tests using examples
|
||||
# Copyright (C) 2020 Ngô Ngọc Đức Huy
|
||||
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of palace.
|
||||
#
|
||||
# palace is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
#
|
||||
# palace is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with palace. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from os import environ
|
||||
from os.path import abspath, dirname, join
|
||||
from platform import system
|
||||
from random import choices
|
||||
from subprocess import PIPE, run, CalledProcessError
|
||||
from sys import executable
|
||||
from uuid import uuid4
|
||||
|
||||
from palace import reverb_preset_names
|
||||
from pytest import mark, raises
|
||||
|
||||
EXAMPLES = abspath(join(dirname(__file__), '..', '..', 'examples'))
|
||||
EVENT = join(EXAMPLES, 'palace-event.py')
|
||||
HRTF = join(EXAMPLES, 'palace-hrtf.py')
|
||||
INFO = join(EXAMPLES, 'palace-info.py')
|
||||
LATENCY = join(EXAMPLES, 'palace-latency.py')
|
||||
REVERB = join(EXAMPLES, 'palace-reverb.py')
|
||||
STDEC = join(EXAMPLES, 'palace-stdec.py')
|
||||
TONEGEN = join(EXAMPLES, 'palace-tonegen.py')
|
||||
|
||||
MADEUP_DEVICE = str(uuid4())
|
||||
REVERB_PRESETS = choices(reverb_preset_names, k=5)
|
||||
WAVEFORMS = ['sine', 'square', 'sawtooth',
|
||||
'triangle', 'impulse', 'white-noise']
|
||||
|
||||
travis_macos = bool(environ.get('TRAVIS')) and system() == 'Darwin'
|
||||
skipif_travis_macos = mark.skipif(travis_macos, reason='Travis CI for macOS')
|
||||
|
||||
|
||||
def capture(*argv):
|
||||
"""Return the captured standard output of given Python script."""
|
||||
return run([executable, *argv], stdout=PIPE).stdout.decode()
|
||||
|
||||
|
||||
@skipif_travis_macos
|
||||
def test_event(aiff, flac, mp3, ogg, wav):
|
||||
"""Test the event handling example."""
|
||||
event = capture(EVENT, aiff, flac, mp3, ogg, wav)
|
||||
assert 'Opened' in event
|
||||
assert f'Playing {aiff}' in event
|
||||
assert f'Playing {flac}' in event
|
||||
assert f'Playing {mp3}' in event
|
||||
assert f'Playing {ogg}' in event
|
||||
assert f'Playing {wav}' in event
|
||||
|
||||
|
||||
@skipif_travis_macos
|
||||
def test_hrtf(ogg):
|
||||
"""Test the HRTF example."""
|
||||
hrtf = capture(HRTF, ogg)
|
||||
assert 'Opened' in hrtf
|
||||
assert f'Playing {ogg}' in hrtf
|
||||
|
||||
|
||||
def test_info():
|
||||
"""Test the information query example."""
|
||||
run([executable, INFO], check=True)
|
||||
with raises(CalledProcessError):
|
||||
run([executable, INFO, MADEUP_DEVICE], check=True)
|
||||
|
||||
|
||||
@skipif_travis_macos
|
||||
def test_latency(mp3):
|
||||
"""Test the latency example."""
|
||||
latency = capture(LATENCY, mp3)
|
||||
assert 'Opened' in latency
|
||||
assert f'Playing {mp3}' in latency
|
||||
assert 'Offset' in latency
|
||||
|
||||
|
||||
@skipif_travis_macos
|
||||
@mark.parametrize('preset', REVERB_PRESETS)
|
||||
def test_reverb(preset, flac):
|
||||
"""Test the reverb example."""
|
||||
reverb = capture(REVERB, flac, '-r', preset)
|
||||
assert 'Opened' in reverb
|
||||
assert f'Playing {flac}' in reverb
|
||||
assert f'Loading reverb preset {preset}' in reverb
|
||||
|
||||
|
||||
@skipif_travis_macos
|
||||
def test_stdec(aiff):
|
||||
"""Test the stdec example."""
|
||||
stdec = capture(STDEC, aiff)
|
||||
assert 'Opened' in stdec
|
||||
assert f'Playing {aiff}' in stdec
|
||||
|
||||
|
||||
@mark.parametrize('waveform', WAVEFORMS)
|
||||
def test_tonegen(waveform):
|
||||
"""Test the tonegen example."""
|
||||
tonegen = capture(TONEGEN, '-l', '0.1', '-w', waveform)
|
||||
assert 'Opened' in tonegen
|
||||
assert f'Playing {waveform}' in tonegen
|
|
@ -0,0 +1,97 @@
|
|||
# Message handling functional tests
|
||||
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of palace.
|
||||
#
|
||||
# palace is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
#
|
||||
# palace is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with palace. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import aifc
|
||||
from os import environ
|
||||
from platform import system
|
||||
from unittest.mock import Mock
|
||||
from uuid import uuid4
|
||||
|
||||
from palace import (channel_configs, sample_types, decode,
|
||||
Device, Context, Buffer, SourceGroup, MessageHandler)
|
||||
from pytest import mark
|
||||
|
||||
|
||||
travis_macos = bool(environ.get('TRAVIS')) and system() == 'Darwin'
|
||||
skipif_travis_macos = mark.skipif(travis_macos, reason='Travis CI for macOS')
|
||||
|
||||
|
||||
def mock(message):
|
||||
"""Return the MessageHandler corresponding to the given message."""
|
||||
return type(''.join(map(str.capitalize, message.split('_'))),
|
||||
(MessageHandler,), {message: Mock()})()
|
||||
|
||||
|
||||
@mark.skip(reason='unknown way of disconnecting device to test this')
|
||||
def test_device_diconnected():
|
||||
"""Test the handling of device disconnected message."""
|
||||
|
||||
|
||||
@skipif_travis_macos
|
||||
def test_source_stopped(wav):
|
||||
"""Test the handling of source stopped message."""
|
||||
with Device() as device, Context(device) as context, Buffer(wav) as buffer:
|
||||
context.message_handler = mock('source_stopped')
|
||||
with buffer.play() as source:
|
||||
while source.playing: pass
|
||||
context.update()
|
||||
context.message_handler.source_stopped.assert_called_with(source)
|
||||
|
||||
|
||||
@skipif_travis_macos
|
||||
def test_source_force_stopped(ogg):
|
||||
"""Test the handling of source force stopped message."""
|
||||
with Device() as device, Context(device) as context:
|
||||
context.message_handler = mock('source_force_stopped')
|
||||
# TODO: test source preempted by a higher-prioritized one
|
||||
with Buffer(ogg) as buffer: source = buffer.play()
|
||||
context.message_handler.source_force_stopped.assert_called_with(source)
|
||||
with SourceGroup() as group, Buffer(ogg) as buffer:
|
||||
source.group = group
|
||||
buffer.play(source)
|
||||
group.stop_all()
|
||||
context.message_handler.source_force_stopped.assert_called_with(source)
|
||||
source.destroy()
|
||||
|
||||
|
||||
@skipif_travis_macos
|
||||
def test_buffer_loading(aiff):
|
||||
"""Test the handling of buffer loading message."""
|
||||
with Device() as device, Context(device) as context:
|
||||
context.message_handler = mock('buffer_loading')
|
||||
with Buffer(aiff), aifc.open(aiff, 'r') as f:
|
||||
args, kwargs = context.message_handler.buffer_loading.call_args
|
||||
name, channel_config, sample_type, sample_rate, data = args
|
||||
assert name == aiff
|
||||
assert channel_config == channel_configs[f.getnchannels()-1]
|
||||
assert sample_type == sample_types[f.getsampwidth()-1]
|
||||
assert sample_rate == f.getframerate()
|
||||
# TODO: verify data
|
||||
|
||||
|
||||
def test_resource_not_found(flac):
|
||||
"""Test the handling of resource not found message."""
|
||||
with Device() as device, Context(device) as context:
|
||||
context.message_handler = mock('resource_not_found')
|
||||
context.message_handler.resource_not_found.return_value = ''
|
||||
name = str(uuid4())
|
||||
try:
|
||||
decode(name)
|
||||
except RuntimeError:
|
||||
pass
|
||||
context.message_handler.resource_not_found.assert_called_with(name)
|
|
@ -1,71 +0,0 @@
|
|||
# Listener pytest module
|
||||
# Copyright (C) 2020 Ngô Xuân Minh
|
||||
#
|
||||
# This file is part of palace.
|
||||
#
|
||||
# palace is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
#
|
||||
# palace is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with palace. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""This pytest module tries to test the correctness of the class Listener."""
|
||||
|
||||
from pytest import raises
|
||||
|
||||
from math import inf
|
||||
|
||||
|
||||
def test_gain(context):
|
||||
"""Test write property gain."""
|
||||
context.listener.gain = 5/7
|
||||
context.listener.gain = 7/5
|
||||
context.listener.gain = 0
|
||||
context.listener.gain = inf
|
||||
with raises(ValueError): context.listener.gain = -1
|
||||
|
||||
|
||||
def test_position(context):
|
||||
"""Test write property position."""
|
||||
context.listener.position = 1, 0, 1
|
||||
context.listener.position = 1, 0, -1
|
||||
context.listener.position = 1, -1, 0
|
||||
context.listener.position = 1, 1, 0
|
||||
context.listener.position = 0, 0, 0
|
||||
context.listener.position = 1, 1, 1
|
||||
|
||||
|
||||
def test_velocity(context):
|
||||
"""Test write property velocity."""
|
||||
context.listener.velocity = 420, 0, 69
|
||||
context.listener.velocity = 69, 0, -420
|
||||
context.listener.velocity = 0, 420, -69
|
||||
context.listener.velocity = 0, 0, 42
|
||||
context.listener.velocity = 0, 0, 0
|
||||
context.listener.velocity = 420, 69, 420
|
||||
|
||||
|
||||
def test_orientaion(context):
|
||||
"""Test write property orientation."""
|
||||
context.listener.orientation = (420, 0, 69), (0, 42, 0)
|
||||
context.listener.orientation = (69, 0, -420), (0, -69, 420)
|
||||
context.listener.orientation = (0, 420, -69), (420, -69, 69)
|
||||
context.listener.orientation = (0, 0, 42), (-420, -420, 0)
|
||||
context.listener.orientation = (0, 0, 0), (-420, -69, -69)
|
||||
context.listener.orientation = (420, 69, 420), (69, -420, 0)
|
||||
|
||||
|
||||
def test_meters_per_unit(context):
|
||||
"""Test write property meter_per_unit."""
|
||||
context.listener.meters_per_unit = 4/9
|
||||
context.listener.meters_per_unit = 9/4
|
||||
with raises(ValueError): context.listener.meters_per_unit = 0
|
||||
context.listener.meters_per_unit = inf
|
||||
with raises(ValueError): context.listener.meters_per_unit = -1
|
|
@ -0,0 +1,38 @@
|
|||
# Test fixtures for unit tests
|
||||
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of palace.
|
||||
#
|
||||
# palace is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
#
|
||||
# palace is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with palace. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""This module provide default objects of palace classes as fixtures
|
||||
for convenient testing.
|
||||
"""
|
||||
|
||||
from pytest import fixture
|
||||
from palace import Device, Context
|
||||
|
||||
|
||||
@fixture(scope='session')
|
||||
def device():
|
||||
"""Provide the default device."""
|
||||
with Device() as dev: yield dev
|
||||
|
||||
|
||||
@fixture(scope='session')
|
||||
def context(device):
|
||||
"""Provide a context creared from the default device
|
||||
(default context).
|
||||
"""
|
||||
with Context(device) as ctx: yield ctx
|
|
@ -1,4 +1,4 @@
|
|||
# single-precision floating-point math
|
||||
# Single-precision floating-point math
|
||||
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of palace.
|
||||
|
@ -22,7 +22,7 @@ for single-precision floating-point numbers.
|
|||
__all__ = ['FLT_MAX', 'allclose', 'isclose']
|
||||
|
||||
from math import isclose as _isclose
|
||||
from typing import Sequence
|
||||
from typing import Any, Callable, Sequence
|
||||
|
||||
FLT_EPSILON: float = 2.0 ** -23
|
||||
FLT_MAX: float = 2.0**128 - 2.0**104
|
||||
|
@ -42,7 +42,8 @@ def isclose(a: float, b: float) -> bool:
|
|||
return _isclose(a, b, rel_tol=FLT_EPSILON)
|
||||
|
||||
|
||||
def allclose(a: Sequence[float], b: Sequence[float]) -> bool:
|
||||
def allclose(a: Sequence[float], b: Sequence[float],
|
||||
close: Callable[[Any, Any], bool] = isclose) -> bool:
|
||||
"""Determine whether two sequences of single-precision
|
||||
floating-point numbers are close in value.
|
||||
|
||||
|
@ -53,4 +54,4 @@ def allclose(a: Sequence[float], b: Sequence[float]) -> bool:
|
|||
That is, NaN is not close to anything, even itself.
|
||||
inf and -inf are only close to themselves.
|
||||
"""
|
||||
return all(map(isclose, a, b))
|
||||
return type(a) is type(b) and all(map(close, a, b))
|
|
@ -1,4 +1,4 @@
|
|||
# Source pytest module
|
||||
# Context pytest module
|
||||
# Copyright (C) 2020 Ngô Ngọc Đức Huy
|
||||
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||
# Copyright (C) 2020 Ngô Xuân Minh
|
||||
|
@ -26,21 +26,29 @@ from pytest import raises
|
|||
from math import inf
|
||||
|
||||
|
||||
def test_with_context(device):
|
||||
"""Test if `with` can be used to start a context
|
||||
and is destroyed properly.
|
||||
"""
|
||||
with Context(device) as context:
|
||||
assert current_context() == context
|
||||
def test_comparison(device):
|
||||
"""Test basic comparisons."""
|
||||
with Context(device) as c0, Context(device) as c1, Context(device) as c2:
|
||||
assert c0 != c1
|
||||
contexts = [c1, c1, c0, c2]
|
||||
contexts.sort()
|
||||
contexts.remove(c2)
|
||||
contexts.remove(c0)
|
||||
assert contexts[0] == contexts[1]
|
||||
|
||||
|
||||
def test_nested_context_manager(device):
|
||||
"""Test if the context manager returns to the
|
||||
previous context.
|
||||
"""
|
||||
def test_bool(device):
|
||||
"""Test boolean value."""
|
||||
with Context(device) as context: assert context
|
||||
assert not context
|
||||
|
||||
|
||||
def test_batch_control(device):
|
||||
"""Test calls of start_batch and end_batch."""
|
||||
with Context(device) as context:
|
||||
with Context(device): pass
|
||||
assert current_context() == context
|
||||
# At the moment these are no-op.
|
||||
context.start_batch()
|
||||
context.end_batch()
|
||||
|
||||
|
||||
def test_message_handler(device):
|
||||
|
@ -61,36 +69,48 @@ def test_async_wake_interval(device):
|
|||
assert context.async_wake_interval == 42
|
||||
|
||||
|
||||
def test_format_support(device):
|
||||
"""Test method is_supported."""
|
||||
with Context(device) as context:
|
||||
assert isinstance(context.is_supported('Rear', '32-bit float'), bool)
|
||||
with raises(ValueError): context.is_supported('Shu', 'Mulaw')
|
||||
with raises(ValueError): context.is_supported('Stereo', 'Type')
|
||||
|
||||
|
||||
def test_default_resampler_index(device):
|
||||
"""Test return values default_resampler_index."""
|
||||
"""Test read-only property default_resampler_index."""
|
||||
with Context(device) as context:
|
||||
index = context.default_resampler_index
|
||||
assert index >= 0
|
||||
assert len(context.available_resamplers) > index
|
||||
with raises(AttributeError): context.available_resamplers = 0
|
||||
|
||||
|
||||
def test_doppler_factor(device):
|
||||
"""Test write property doppler_factor."""
|
||||
"""Test write-only property doppler_factor."""
|
||||
with Context(device) as context:
|
||||
context.doppler_factor = 4/9
|
||||
context.doppler_factor = 9/4
|
||||
context.doppler_factor = 0
|
||||
context.doppler_factor = inf
|
||||
with raises(ValueError): context.doppler_factor = -1
|
||||
with raises(AttributeError): context.doppler_factor
|
||||
|
||||
|
||||
def test_speed_of_sound(device):
|
||||
"""Test write property speed_of_sound."""
|
||||
"""Test write-only property speed_of_sound."""
|
||||
with Context(device) as context:
|
||||
context.speed_of_sound = 5/7
|
||||
context.speed_of_sound = 7/5
|
||||
with raises(ValueError): context.speed_of_sound = 0
|
||||
context.speed_of_sound = inf
|
||||
with raises(ValueError): context.speed_of_sound = -1
|
||||
with raises(AttributeError): context.speed_of_sound
|
||||
|
||||
|
||||
def test_distance_model(device):
|
||||
"""Test preset values distance_model."""
|
||||
"""Test write-only distance_model."""
|
||||
with Context(device) as context:
|
||||
for model in distance_models: context.distance_model = model
|
||||
with raises(ValueError): context.distance_model = 'EYYYYLMAO'
|
||||
with raises(AttributeError): context.distance_model
|
|
@ -0,0 +1,437 @@
|
|||
# Effect pytest module
|
||||
# Copyright (C) 2020 Ngô Ngọc Đức Huy
|
||||
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of palace.
|
||||
#
|
||||
# palace is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
#
|
||||
# palace is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with palace. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""This pytest module verifies environmental effects."""
|
||||
|
||||
from palace import BaseEffect, ChorusEffect, ReverbEffect, Source
|
||||
from pytest import raises
|
||||
|
||||
from fmath import isclose, allclose
|
||||
|
||||
|
||||
def test_slot_gain(context):
|
||||
"""Test write-only property slot_gain."""
|
||||
with BaseEffect() as fx:
|
||||
fx.slot_gain = 0
|
||||
fx.slot_gain = 1
|
||||
fx.slot_gain = 5/7
|
||||
with raises(ValueError): fx.slot_gain = 7/5
|
||||
with raises(ValueError): fx.slot_gain = -1
|
||||
|
||||
|
||||
def test_source_sends(context):
|
||||
"""Test property source_sends by assigning it to a source."""
|
||||
with Source() as src, BaseEffect() as fx:
|
||||
src.sends[0].effect = fx
|
||||
assert fx.source_sends[-1] == (src, 0)
|
||||
|
||||
|
||||
def test_use_count(context):
|
||||
"""Test read-only property use_count."""
|
||||
with BaseEffect() as fx:
|
||||
assert fx.use_count == len(fx.source_sends)
|
||||
|
||||
|
||||
def test_reverb(context):
|
||||
"""Test ReverbEffect initialization."""
|
||||
with ReverbEffect('DRUGGED'): pass
|
||||
with raises(ValueError):
|
||||
with ReverbEffect('NOT_AN_EFFECT'): pass
|
||||
|
||||
|
||||
def test_reverb_send_auto(context):
|
||||
"""Test ReverbEffect's write-only property send_auto."""
|
||||
with ReverbEffect() as fx:
|
||||
fx.send_auto = False
|
||||
fx.send_auto = True
|
||||
|
||||
|
||||
def test_reverb_density(context):
|
||||
"""Test ReverbEffect's property density."""
|
||||
with ReverbEffect() as fx:
|
||||
assert fx.density == 1
|
||||
fx.density = 5/7
|
||||
assert isclose(fx.density, 5/7)
|
||||
fx.density = 0
|
||||
assert fx.density == 0
|
||||
fx.density = 1
|
||||
assert fx.density == 1
|
||||
with raises(ValueError): fx.density = 7/5
|
||||
with raises(ValueError): fx.density = -1
|
||||
|
||||
|
||||
def test_reverb_diffusion(context):
|
||||
"""Test ReverbEffect's property diffusion."""
|
||||
with ReverbEffect() as fx:
|
||||
assert fx.diffusion == 1
|
||||
fx.diffusion = 5/7
|
||||
assert isclose(fx.diffusion, 5/7)
|
||||
fx.diffusion = 0
|
||||
assert fx.diffusion == 0
|
||||
fx.diffusion = 1
|
||||
assert fx.diffusion == 1
|
||||
with raises(ValueError): fx.diffusion = 7/5
|
||||
with raises(ValueError): fx.diffusion = -1
|
||||
|
||||
|
||||
def test_reverb_gain(context):
|
||||
"""Test ReverbEffect's property gain."""
|
||||
with ReverbEffect() as fx:
|
||||
assert isclose(fx.gain, 0.3162)
|
||||
fx.gain = 5/7
|
||||
assert isclose(fx.gain, 5/7)
|
||||
fx.gain = 0
|
||||
assert fx.gain == 0
|
||||
fx.gain = 1
|
||||
assert fx.gain == 1
|
||||
with raises(ValueError): fx.gain = 7/5
|
||||
with raises(ValueError): fx.gain = -1
|
||||
|
||||
|
||||
def test_reverb_gain_hf(context):
|
||||
"""Test ReverbEffect's property gain_hf."""
|
||||
with ReverbEffect() as fx:
|
||||
assert isclose(fx.gain_hf, 0.8913)
|
||||
fx.gain_hf = 5/7
|
||||
assert isclose(fx.gain_hf, 5/7)
|
||||
fx.gain_hf = 0
|
||||
assert fx.gain_hf == 0
|
||||
fx.gain_hf = 1
|
||||
assert fx.gain_hf == 1
|
||||
with raises(ValueError): fx.gain_hf = 7/5
|
||||
with raises(ValueError): fx.gain_hf = -1
|
||||
|
||||
|
||||
def test_reverb_gain_lf(context):
|
||||
"""Test ReverbEffect's property gain_lf."""
|
||||
with ReverbEffect() as fx:
|
||||
assert fx.gain_lf == 1
|
||||
fx.gain_lf = 5/7
|
||||
assert isclose(fx.gain_lf, 5/7)
|
||||
fx.gain_lf = 0
|
||||
assert fx.gain_lf == 0
|
||||
fx.gain_lf = 1
|
||||
assert fx.gain_lf == 1
|
||||
with raises(ValueError): fx.gain_lf = 7/5
|
||||
with raises(ValueError): fx.gain_lf = -1
|
||||
|
||||
|
||||
def test_reverb_decay_time(context):
|
||||
"""Test ReverbEffect's property decay_time."""
|
||||
with ReverbEffect() as fx:
|
||||
assert isclose(fx.decay_time, 1.49)
|
||||
fx.decay_time = 5/7
|
||||
assert isclose(fx.decay_time, 5/7)
|
||||
fx.decay_time = 0.1
|
||||
assert isclose(fx.decay_time, 0.1)
|
||||
fx.decay_time = 20
|
||||
assert fx.decay_time == 20
|
||||
with raises(ValueError): fx.decay_time = 21
|
||||
with raises(ValueError): fx.decay_time = -1
|
||||
|
||||
|
||||
def test_reverb_decay_hf_ratio(context):
|
||||
"""Test ReverbEffect's property decay_hf_ratio."""
|
||||
with ReverbEffect() as fx:
|
||||
assert isclose(fx.decay_hf_ratio, 0.83)
|
||||
fx.decay_hf_ratio = 5/7
|
||||
assert isclose(fx.decay_hf_ratio, 5/7)
|
||||
fx.decay_hf_ratio = 0.1
|
||||
assert isclose(fx.decay_hf_ratio, 0.1)
|
||||
fx.decay_hf_ratio = 2
|
||||
assert fx.decay_hf_ratio == 2
|
||||
with raises(ValueError): fx.decay_hf_ratio = 21
|
||||
with raises(ValueError): fx.decay_hf_ratio = -1
|
||||
|
||||
|
||||
def test_reverb_decay_lf_ratio(context):
|
||||
"""Test ReverbEffect's property decay_lf_ratio."""
|
||||
with ReverbEffect() as fx:
|
||||
assert fx.decay_lf_ratio == 1
|
||||
fx.decay_lf_ratio = 5/7
|
||||
assert isclose(fx.decay_lf_ratio, 5/7)
|
||||
fx.decay_lf_ratio = 0.1
|
||||
assert isclose(fx.decay_lf_ratio, 0.1)
|
||||
fx.decay_lf_ratio = 2
|
||||
assert fx.decay_lf_ratio == 2
|
||||
with raises(ValueError): fx.decay_lf_ratio = 21
|
||||
with raises(ValueError): fx.decay_lf_ratio = -1
|
||||
|
||||
|
||||
def test_reverb_reflections_gain(context):
|
||||
"""Test ReverbEffect's property reflections_gain."""
|
||||
with ReverbEffect() as fx:
|
||||
assert isclose(fx.reflections_gain, 0.05)
|
||||
fx.reflections_gain = 5/7
|
||||
assert isclose(fx.reflections_gain, 5/7)
|
||||
fx.reflections_gain = 3.16
|
||||
assert isclose(fx.reflections_gain, 3.16)
|
||||
fx.reflections_gain = 0
|
||||
assert fx.reflections_gain == 0
|
||||
with raises(ValueError): fx.reflections_gain = 4
|
||||
with raises(ValueError): fx.reflections_gain = -1
|
||||
|
||||
|
||||
def test_reverb_reflections_delay(context):
|
||||
"""Test ReverbEffect's property reflections_delay."""
|
||||
with ReverbEffect() as fx:
|
||||
assert isclose(fx.reflections_delay, 0.007)
|
||||
fx.reflections_delay = 0.3
|
||||
assert isclose(fx.reflections_delay, 0.3)
|
||||
fx.reflections_delay = 0
|
||||
assert fx.reflections_delay == 0
|
||||
with raises(ValueError): fx.reflections_delay = 1
|
||||
with raises(ValueError): fx.reflections_delay = -1
|
||||
|
||||
|
||||
def test_reverb_reflections_pan(context):
|
||||
"""Test ReverbEffect's property reflections_pan."""
|
||||
with ReverbEffect() as fx:
|
||||
assert allclose(fx.reflections_pan, (0, 0, 0))
|
||||
fx.reflections_pan = 5/7, -69/420, 6/9
|
||||
assert allclose(fx.reflections_pan, (5/7, -69/420, 6/9))
|
||||
with raises(ValueError): fx.reflections_pan = 1, 1, 1
|
||||
with raises(ValueError): fx.reflections_pan = 0, 0, 2
|
||||
with raises(ValueError): fx.reflections_pan = 0, 2, 0
|
||||
with raises(ValueError): fx.reflections_pan = 2, 0, 0
|
||||
with raises(ValueError): fx.reflections_pan = 0, 0, -2
|
||||
with raises(ValueError): fx.reflections_pan = 0, -2, 0
|
||||
with raises(ValueError): fx.reflections_pan = -2, 0, 0
|
||||
|
||||
|
||||
def test_reverb_late_reverb_gain(context):
|
||||
"""Test ReverbEffect's property late_reverb_gain."""
|
||||
with ReverbEffect() as fx:
|
||||
assert isclose(fx.late_reverb_gain, 1.2589)
|
||||
fx.late_reverb_gain = 5/7
|
||||
assert isclose(fx.late_reverb_gain, 5/7)
|
||||
fx.late_reverb_gain = 0
|
||||
assert fx.late_reverb_gain == 0
|
||||
fx.late_reverb_gain = 10
|
||||
assert fx.late_reverb_gain == 10
|
||||
with raises(ValueError): fx.late_reverb_gain = 11
|
||||
with raises(ValueError): fx.late_reverb_gain = -1
|
||||
|
||||
|
||||
def test_reverb_late_reverb_delay(context):
|
||||
"""Test ReverbEffect's property late_reverb_delay."""
|
||||
with ReverbEffect() as fx:
|
||||
assert isclose(fx.late_reverb_delay, 0.011)
|
||||
fx.late_reverb_delay = 0.05
|
||||
assert isclose(fx.late_reverb_delay, 0.05)
|
||||
fx.late_reverb_delay = 0
|
||||
assert fx.late_reverb_delay == 0
|
||||
fx.late_reverb_delay = 0.1
|
||||
assert isclose(fx.late_reverb_delay, 0.1)
|
||||
with raises(ValueError): fx.late_reverb_delay = 1
|
||||
with raises(ValueError): fx.late_reverb_delay = -1
|
||||
|
||||
|
||||
def test_reverb_late_reverb_pan(context):
|
||||
"""Test ReverbEffect's property late_reverb_pan."""
|
||||
with ReverbEffect() as fx:
|
||||
assert allclose(fx.late_reverb_pan, (0, 0, 0))
|
||||
fx.late_reverb_pan = 5/7, -69/420, 6/9
|
||||
assert allclose(fx.late_reverb_pan, (5/7, -69/420, 6/9))
|
||||
with raises(ValueError): fx.late_reverb_pan = 1, 1, 1
|
||||
with raises(ValueError): fx.late_reverb_pan = 0, 0, 2
|
||||
with raises(ValueError): fx.late_reverb_pan = 0, 2, 0
|
||||
with raises(ValueError): fx.late_reverb_pan = 2, 0, 0
|
||||
with raises(ValueError): fx.late_reverb_pan = 0, 0, -2
|
||||
with raises(ValueError): fx.late_reverb_pan = 0, -2, 0
|
||||
with raises(ValueError): fx.late_reverb_pan = -2, 0, 0
|
||||
|
||||
|
||||
def test_reverb_echo_time(context):
|
||||
"""Test ReverbEffect's property echo_time."""
|
||||
with ReverbEffect() as fx:
|
||||
assert isclose(fx.echo_time, 0.25)
|
||||
fx.echo_time = 0.075
|
||||
assert isclose(fx.echo_time, 0.075)
|
||||
fx.echo_time = 0.1
|
||||
assert isclose(fx.echo_time, 0.1)
|
||||
with raises(ValueError): fx.echo_time = 0.05
|
||||
with raises(ValueError): fx.echo_time = 0.5
|
||||
|
||||
|
||||
def test_reverb_echo_depth(context):
|
||||
"""Test ReverbEffect's property echo_depth."""
|
||||
with ReverbEffect() as fx:
|
||||
assert fx.echo_depth == 0
|
||||
fx.echo_depth = 5/7
|
||||
assert isclose(fx.echo_depth, 5/7)
|
||||
fx.echo_depth = 0
|
||||
assert fx.echo_depth == 0
|
||||
fx.echo_depth = 1
|
||||
assert fx.echo_depth == 1
|
||||
with raises(ValueError): fx.echo_depth = 7/5
|
||||
with raises(ValueError): fx.echo_depth = -1
|
||||
|
||||
|
||||
def test_reverb_modulation_time(context):
|
||||
"""Test ReverbEffect's property modulation_time."""
|
||||
with ReverbEffect() as fx:
|
||||
assert isclose(fx.modulation_time, 0.25)
|
||||
fx.modulation_time = 5/7
|
||||
assert isclose(fx.modulation_time, 5/7)
|
||||
fx.modulation_time = 0.04
|
||||
assert isclose(fx.modulation_time, 0.04)
|
||||
fx.modulation_time = 4
|
||||
assert fx.modulation_time == 4
|
||||
with raises(ValueError): fx.modulation_time = 4.2
|
||||
with raises(ValueError): fx.modulation_time = 0
|
||||
|
||||
|
||||
def test_reverb_modulation_depth(context):
|
||||
"""Test ReverbEffect's property modulation_depth."""
|
||||
with ReverbEffect() as fx:
|
||||
assert fx.modulation_depth == 0
|
||||
fx.modulation_depth = 5/7
|
||||
assert isclose(fx.modulation_depth, 5/7)
|
||||
fx.modulation_depth = 0
|
||||
assert fx.modulation_depth == 0
|
||||
fx.modulation_depth = 1
|
||||
assert fx.modulation_depth == 1
|
||||
with raises(ValueError): fx.modulation_depth = 7/5
|
||||
with raises(ValueError): fx.modulation_depth = -1
|
||||
|
||||
|
||||
def test_reverb_air_absorption_gain_hf(context):
|
||||
"""Test ReverbEffect's property air_absorption_gain_hf."""
|
||||
with ReverbEffect() as fx:
|
||||
assert isclose(fx.air_absorption_gain_hf, 0.9943)
|
||||
fx.air_absorption_gain_hf = 0.999
|
||||
assert isclose(fx.air_absorption_gain_hf, 0.999)
|
||||
fx.air_absorption_gain_hf = 0.892
|
||||
assert isclose(fx.air_absorption_gain_hf, 0.892)
|
||||
fx.air_absorption_gain_hf = 1
|
||||
assert fx.air_absorption_gain_hf == 1
|
||||
with raises(ValueError): fx.air_absorption_gain_hf = 7/5
|
||||
with raises(ValueError): fx.air_absorption_gain_hf = 0.5
|
||||
|
||||
|
||||
def test_reverb_hf_reference(context):
|
||||
"""Test ReverbEffect's property hf_reference."""
|
||||
with ReverbEffect() as fx:
|
||||
assert fx.hf_reference == 5000
|
||||
fx.hf_reference = 6969
|
||||
assert fx.hf_reference == 6969
|
||||
fx.hf_reference = 1000
|
||||
assert fx.hf_reference == 1000
|
||||
fx.hf_reference = 20000
|
||||
assert fx.hf_reference == 20000
|
||||
with raises(ValueError): fx.hf_reference = 20000.5
|
||||
with raises(ValueError): fx.hf_reference = 999
|
||||
|
||||
|
||||
def test_reverb_lf_reference(context):
|
||||
"""Test ReverbEffect's property lf_reference."""
|
||||
with ReverbEffect() as fx:
|
||||
assert fx.lf_reference == 250
|
||||
fx.lf_reference = 666
|
||||
assert fx.lf_reference == 666
|
||||
fx.lf_reference = 1000
|
||||
assert fx.lf_reference == 1000
|
||||
fx.lf_reference = 20
|
||||
assert fx.lf_reference == 20
|
||||
with raises(ValueError): fx.lf_reference = 19.5
|
||||
with raises(ValueError): fx.lf_reference = 1001
|
||||
|
||||
|
||||
def test_reverb_room_rolloff_factor(context):
|
||||
"""Test ReverbEffect's property room_rolloff_factor."""
|
||||
with ReverbEffect() as fx:
|
||||
assert fx.room_rolloff_factor == 0
|
||||
fx.room_rolloff_factor = 9/6
|
||||
assert fx.room_rolloff_factor == 9/6
|
||||
fx.room_rolloff_factor = 0
|
||||
assert fx.room_rolloff_factor == 0
|
||||
fx.room_rolloff_factor = 10
|
||||
assert fx.room_rolloff_factor == 10
|
||||
with raises(ValueError): fx.room_rolloff_factor = 10.5
|
||||
with raises(ValueError): fx.room_rolloff_factor = -1
|
||||
|
||||
|
||||
def test_reverb_decay_hf_limit(context):
|
||||
"""Test ReverbEffect's property decay_hf_limit."""
|
||||
with ReverbEffect() as fx:
|
||||
assert fx.decay_hf_limit is True
|
||||
fx.decay_hf_limit = False
|
||||
assert fx.decay_hf_limit is False
|
||||
fx.decay_hf_limit = True
|
||||
assert fx.decay_hf_limit is True
|
||||
|
||||
|
||||
def test_chorus_waveform(context):
|
||||
"""Test ChorusEffect's property waveform."""
|
||||
with ChorusEffect() as fx:
|
||||
assert fx.waveform == 'triangle'
|
||||
fx.waveform = 'sine'
|
||||
assert fx.waveform == 'sine'
|
||||
fx.waveform = 'triangle'
|
||||
assert fx.waveform == 'triangle'
|
||||
with raises(ValueError): fx.waveform = 'ABC'
|
||||
|
||||
|
||||
def test_chorus_phase(context):
|
||||
"""Test ChorusEffect's property phase."""
|
||||
with ChorusEffect() as fx:
|
||||
assert fx.phase == 90
|
||||
fx.phase = 180
|
||||
assert fx.phase == 180
|
||||
fx.phase = -180
|
||||
assert fx.phase == -180
|
||||
with raises(ValueError): fx.phase = 181
|
||||
with raises(ValueError): fx.phase = -181
|
||||
|
||||
|
||||
def test_chorus_depth(context):
|
||||
"""Test ChorusEffect's property depth."""
|
||||
with ChorusEffect() as fx:
|
||||
assert isclose(fx.depth, 0.1)
|
||||
fx.depth = 0
|
||||
assert fx.depth == 0
|
||||
fx.depth = 1
|
||||
assert fx.depth == 1
|
||||
with raises(ValueError): fx.depth = 2
|
||||
with raises(ValueError): fx.depth = -1
|
||||
|
||||
|
||||
def test_chorus_feedback(context):
|
||||
"""Test ChorusEffect's property feedback."""
|
||||
with ChorusEffect() as fx:
|
||||
assert isclose(fx.feedback, 0.25)
|
||||
fx.feedback = -1
|
||||
assert fx.feedback == -1
|
||||
fx.feedback = 1
|
||||
assert fx.feedback == 1
|
||||
with raises(ValueError): fx.feedback = 3/2
|
||||
with raises(ValueError): fx.feedback = -7/5
|
||||
|
||||
|
||||
def test_chorus_delay(context):
|
||||
"""Test ChorusEffect's property delay."""
|
||||
with ChorusEffect() as fx:
|
||||
assert isclose(fx.delay, 0.016)
|
||||
fx.delay = 0
|
||||
assert fx.delay == 0
|
||||
fx.delay = 0.016
|
||||
assert isclose(fx.delay, 0.016)
|
||||
with raises(ValueError): fx.delay = 0.017
|
||||
with raises(ValueError): fx.delay = -0.1
|
|
@ -0,0 +1,70 @@
|
|||
# Listener pytest module
|
||||
# Copyright (C) 2020 Ngô Xuân Minh
|
||||
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of palace.
|
||||
#
|
||||
# palace is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
#
|
||||
# palace is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with palace. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
"""This pytest module tries to test the correctness of the class Listener."""
|
||||
|
||||
from pytest import mark, raises
|
||||
|
||||
from math import inf
|
||||
|
||||
|
||||
def test_gain(context):
|
||||
"""Test write-only property gain."""
|
||||
context.listener.gain = 5/7
|
||||
context.listener.gain = 7/5
|
||||
context.listener.gain = 0
|
||||
context.listener.gain = inf
|
||||
with raises(ValueError): context.listener.gain = -1
|
||||
with raises(AttributeError): context.listener.gain
|
||||
|
||||
|
||||
@mark.parametrize('position', [(1, 0, 1), (1, 0, -1), (1, -1, 0),
|
||||
(1, 1, 0), (0, 0, 0), (1, 1, 1)])
|
||||
def test_position(context, position):
|
||||
"""Test write-only property position."""
|
||||
context.listener.position = position
|
||||
with raises(AttributeError): context.listener.position
|
||||
|
||||
|
||||
@mark.parametrize('velocity', [(420, 0, 69), (69, 0, -420), (0, 420, -69),
|
||||
(0, 0, 42), (0, 0, 0), (420, 69, 420)])
|
||||
def test_velocity(context, velocity):
|
||||
"""Test write-only property velocity."""
|
||||
context.listener.velocity = velocity
|
||||
with raises(AttributeError): context.listener.velocity
|
||||
|
||||
|
||||
@mark.parametrize(('at', 'up'), [
|
||||
((420, 0, 69), (0, 42, 0)), ((69, 0, -420), (0, -69, 420)),
|
||||
((0, 420, -69), (420, -69, 69)), ((0, 0, 42), (-420, -420, 0)),
|
||||
((0, 0, 0), (-420, -69, -69)), ((420, 69, 420), (69, -420, 0))])
|
||||
def test_orientaion(context, at, up):
|
||||
"""Test write-only property orientation."""
|
||||
context.listener.orientation = at, up
|
||||
with raises(AttributeError): context.listener.orientation
|
||||
|
||||
|
||||
def test_meters_per_unit(context):
|
||||
"""Test write-only property meters_per_unit."""
|
||||
context.listener.meters_per_unit = 4/9
|
||||
context.listener.meters_per_unit = 9/4
|
||||
with raises(ValueError): context.listener.meters_per_unit = 0
|
||||
context.listener.meters_per_unit = inf
|
||||
with raises(ValueError): context.listener.meters_per_unit = -1
|
||||
with raises(AttributeError): context.listener.meters_per_unit
|
|
@ -18,16 +18,59 @@
|
|||
|
||||
"""This pytest module tries to test the correctness of the class Source."""
|
||||
|
||||
from itertools import product, repeat
|
||||
from itertools import permutations, product, repeat
|
||||
from math import inf, pi
|
||||
from operator import is_
|
||||
from random import random, shuffle
|
||||
|
||||
from palace import Source, SourceGroup
|
||||
from palace import Buffer, BaseEffect, Source, SourceGroup
|
||||
from pytest import raises
|
||||
|
||||
from fmath import FLT_MAX, allclose, isclose
|
||||
|
||||
|
||||
def test_comparison(context):
|
||||
"""Test basic comparisons."""
|
||||
with Source() as source0, Source() as source1, Source() as source2:
|
||||
assert source0 != source1
|
||||
sources = [source1, source1, source0, source2]
|
||||
sources.sort()
|
||||
sources.remove(source2)
|
||||
sources.remove(source0)
|
||||
assert sources[0] == sources[1]
|
||||
|
||||
|
||||
def test_bool(context):
|
||||
"""Test boolean value."""
|
||||
with Source() as source: assert source
|
||||
assert not source
|
||||
|
||||
|
||||
def test_control(context, flac):
|
||||
"""Test calling control methods."""
|
||||
with Buffer(flac) as buffer, buffer.play() as source:
|
||||
assert source.playing
|
||||
assert not source.paused
|
||||
source.pause()
|
||||
assert source.paused
|
||||
assert not source.playing
|
||||
source.resume()
|
||||
assert source.playing
|
||||
assert not source.paused
|
||||
source.stop()
|
||||
assert not source.playing
|
||||
assert not source.paused
|
||||
with raises(AttributeError): source.playing = True
|
||||
with raises(AttributeError): source.paused = True
|
||||
|
||||
|
||||
def test_fade_out_to_stop(context, mp3):
|
||||
"""Test calling method fade_out_to_stop."""
|
||||
with Buffer(mp3) as buffer, buffer.play() as source:
|
||||
source.fade_out_to_stop(5/7, buffer.length>>1)
|
||||
with raises(ValueError): source.fade_out_to_stop(0.42, -1)
|
||||
|
||||
|
||||
def test_group(context):
|
||||
"""Test read-write property group."""
|
||||
with Source(context) as source, SourceGroup(context) as source_group:
|
||||
|
@ -47,11 +90,39 @@ def test_priority(context):
|
|||
assert source.priority == 42
|
||||
|
||||
|
||||
def test_offset(context):
|
||||
def test_offset(context, ogg):
|
||||
"""Test read-write property offset."""
|
||||
with Source(context) as source:
|
||||
with Buffer(ogg) as buffer, buffer.play() as source:
|
||||
assert source.offset == 0
|
||||
# TODO: give the source a decoder to seek
|
||||
length = buffer.length
|
||||
source.offset = length >> 1
|
||||
assert source.offset == length >> 1
|
||||
with raises(RuntimeError): source.offset = length
|
||||
with raises(OverflowError): source.offset = -1
|
||||
|
||||
|
||||
def test_offset_seconds(context, flac):
|
||||
"""Test read-only property offset_seconds."""
|
||||
with Buffer(flac) as buffer, buffer.play() as source:
|
||||
assert isinstance(source.offset_seconds, float)
|
||||
with raises(AttributeError):
|
||||
source.offset_seconds = buffer.length_seconds / 2
|
||||
|
||||
|
||||
def test_latency(context, aiff):
|
||||
"""Test read-only property latency."""
|
||||
with Buffer(aiff) as buffer, buffer.play() as source:
|
||||
assert isinstance(source.latency, int)
|
||||
with raises(AttributeError):
|
||||
source.latency = 42
|
||||
|
||||
|
||||
def test_latency_seconds(context, mp3):
|
||||
"""Test read-only property latency_seconds."""
|
||||
with Buffer(mp3) as buffer, buffer.play() as source:
|
||||
assert isinstance(source.latency_seconds, float)
|
||||
with raises(AttributeError):
|
||||
source.latency_seconds = buffer.length_seconds / 2
|
||||
|
||||
|
||||
def test_looping(context):
|
||||
|
@ -129,9 +200,9 @@ def test_velocity(context):
|
|||
def test_orientation(context):
|
||||
"""Test read-write property orientation."""
|
||||
with Source(context) as source:
|
||||
assert all(map(allclose, source.orientation, ((0, 0, -1), (0, 1, 0))))
|
||||
assert allclose(source.orientation, ((0, 0, -1), (0, 1, 0)), allclose)
|
||||
source.orientation = (1, 1, -2), (3, -5, 8)
|
||||
assert all(map(allclose, source.orientation, ((1, 1, -2), (3, -5, 8))))
|
||||
assert allclose(source.orientation, ((1, 1, -2), (3, -5, 8)), allclose)
|
||||
|
||||
|
||||
def test_cone_angles(context):
|
||||
|
@ -220,8 +291,8 @@ def test_spatialize(context):
|
|||
|
||||
def test_resampler_index(context):
|
||||
"""Test read-write property resampler_index."""
|
||||
with Source(context) as source:
|
||||
# TODO: test initial value
|
||||
with Source() as source:
|
||||
assert source.resampler_index == context.default_resampler_index
|
||||
with raises(ValueError): source.resampler_index = -1
|
||||
source.resampler_index = 69
|
||||
assert source.resampler_index == 69
|
||||
|
@ -244,3 +315,31 @@ def test_gain_auto(context):
|
|||
for gain_auto in product(*repeat((False, True), 3)):
|
||||
source.gain_auto = gain_auto
|
||||
assert all(map(is_, source.gain_auto, gain_auto))
|
||||
|
||||
|
||||
def tests_sends(device, context):
|
||||
"""Test send paths assignment."""
|
||||
with Source() as source, BaseEffect() as effect:
|
||||
invalid_filter = [-1, 0, 1]
|
||||
for i in range(device.max_auxiliary_sends):
|
||||
source.sends[i].effect = effect
|
||||
source.sends[i].filter = random(), random(), random()
|
||||
shuffle(invalid_filter)
|
||||
with raises(ValueError): source.sends[i].filter = invalid_filter
|
||||
with raises(AttributeError): source.sends[i].effect
|
||||
with raises(AttributeError): source.sends[i].filter
|
||||
with raises(IndexError): source.sends[-1]
|
||||
with raises(TypeError): source.sends[4.2]
|
||||
with raises(TypeError): source.sends['0']
|
||||
with raises(TypeError): source.sends[6:9]
|
||||
with raises(AttributeError): source.sends = ...
|
||||
|
||||
|
||||
def test_filter(context):
|
||||
"""Test write-only property filter."""
|
||||
with Source() as source:
|
||||
with raises(AttributeError): source.filter
|
||||
source.filter = 1, 6.9, 5/7
|
||||
source.filter = 0, 0, 0
|
||||
for gain, gain_hf, gain_lf in permutations([4, -2, 0]):
|
||||
with raises(ValueError): source.filter = gain, gain_hf, gain_lf
|
17
tox.ini
17
tox.ini
|
@ -1,18 +1,21 @@
|
|||
[tox]
|
||||
envlist = py
|
||||
minversion = 3.3
|
||||
isolated_build = true
|
||||
isolated_build = True
|
||||
|
||||
[testenv]
|
||||
setenv =
|
||||
CYTHON_TRACE = 1
|
||||
deps =
|
||||
flake8
|
||||
Cython
|
||||
scipy
|
||||
pytest-cov
|
||||
commands =
|
||||
flake8
|
||||
pytest
|
||||
commands = pytest
|
||||
setenv = CYTHON_TRACE = 1
|
||||
passenv = TRAVIS
|
||||
|
||||
[testenv:lint]
|
||||
skip_install = true
|
||||
deps = flake8
|
||||
commands = flake8
|
||||
|
||||
[flake8]
|
||||
filename = *.pxd, *.pyx, *.py
|
||||
|
|
Loading…
Reference in New Issue