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
|
#!/bin/sh
|
||||||
set -ev
|
set -ex
|
||||||
git clone --depth 1 https://github.com/kcat/alure /tmp/alure
|
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 \
|
||||||
OPENALDIR=$(brew --prefix openal-soft) cmake -DCMAKE_FIND_FRAMEWORK=NEVER ..
|
-S /tmp/alure -B /tmp/alure/build
|
||||||
cmake --build . --config Release
|
sudo cmake --build /tmp/alure/build --parallel $(sysctl -n hw.ncpu) \
|
||||||
sudo cmake --install .
|
--config Release --target install
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -ev
|
set -ex
|
||||||
yum install -y git cmake pulseaudio \
|
yum install -y git cmake pulseaudio \
|
||||||
alsa-lib-devel pulseaudio-libs-devel jack-audio-connection-kit-devel \
|
alsa-lib-devel pulseaudio-libs-devel jack-audio-connection-kit-devel \
|
||||||
libvorbis-devel opusfile-devel libsndfile-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:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
- /^\d+(\.\d+)+((a|b|rc)\d+)?(\.post\d+)?(\.dev\d+)?$/
|
- /^\d+(\.\d+)+((a|b|rc)\d+)?(\.post\d+)?(\.dev\d+)?$/
|
||||||
|
|
||||||
|
language: python
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- TWINE_USERNAME=__token__
|
- TWINE_USERNAME=__token__
|
||||||
- MACOSX_DEPLOYMENT_TARGET=10.9
|
- 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_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_REQUIRES=tox
|
||||||
- CIBW_TEST_COMMAND_MACOS="tox -c /Users/travis/build/McSinyx/palace"
|
- CIBW_TEST_COMMAND="tox -c {project}"
|
||||||
- CIBW_TEST_COMMAND_LINUX="tox -c /project"
|
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
homebrew:
|
homebrew:
|
||||||
|
@ -32,16 +32,14 @@ jobs:
|
||||||
osx_image: xcode11.3
|
osx_image: xcode11.3
|
||||||
language: shell
|
language: shell
|
||||||
env: CIBW_BUILD=cp36-macosx_x86_64
|
env: CIBW_BUILD=cp36-macosx_x86_64
|
||||||
- os: osx
|
|
||||||
osx_image: xcode11.3
|
|
||||||
language: shell
|
|
||||||
env: CIBW_BUILD=cp37-macosx_x86_64
|
|
||||||
- services: docker
|
- services: docker
|
||||||
env: CIBW_BUILD=cp36-manylinux_x86_64
|
env: CIBW_BUILD="cp36-manylinux_x86_64 cp36-manylinux_aarch64"
|
||||||
- services: docker
|
- services: docker
|
||||||
env: CIBW_BUILD=cp37-manylinux_x86_64
|
env: CIBW_BUILD="cp37-manylinux_x86_64 cp37-manylinux_aarch64"
|
||||||
- services: docker
|
- 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
|
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]
|
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):
|
is to OpenGL (except that all the heavy-lifting are taken are by alure):
|
||||||
|
|
||||||
* 3D sound rendering
|
* 3D positional sound rendering
|
||||||
* Environmental audio effects: reverb, atmospheric air absorption,
|
* Environmental effects: reverb, atmospheric air absorption,
|
||||||
sound occlusion and obstruction
|
sound occlusion and obstruction
|
||||||
* Binaural (HRTF) rendering
|
* Binaural (HRTF) rendering
|
||||||
* Out-of-the-box audio decoding of FLAC, MP3, Ogg Vorbis, Opus, WAV, AIFF, etc.
|
* 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
|
type annotation
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
@ -28,8 +28,8 @@ Palace can be install from the [Python Package Index][PyPI] via simply
|
||||||
pip install palace
|
pip install palace
|
||||||
|
|
||||||
Wheel distributions are built exclusively for amd64. Currently, only GNU/Linux
|
Wheel distributions are built exclusively for amd64. Currently, only GNU/Linux
|
||||||
is properly supported. If you want to help packaging for Windows and macOS,
|
and macOS are properly supported. If you want to help packaging for Windows,
|
||||||
see [GH-1] and [GH-63] respectively on our issues tracker on GitHub.
|
please see [GH-1] on our issues tracker on GitHub.
|
||||||
|
|
||||||
### From source
|
### From source
|
||||||
Aside from the build dependencies listed in `pyproject.toml`, one will
|
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
|
For further information, Python's `help` is your friend and
|
||||||
the API is also available for [online reference][API].
|
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
|
## License and Credits
|
||||||
Palace is free software: you can redistribute it and/or modify it
|
Palace is free software: you can redistribute it and/or modify it
|
||||||
under the terms of the [GNU Lesser General Public License][LGPLv3+]
|
under the terms of the [GNU Lesser General Public License][LGPLv3+]
|
||||||
as published by the Free Software Foundation, either version 3
|
as published by the Free Software Foundation, either version 3
|
||||||
of the License, or (at your option) any later version.
|
of the License, or (at your option) any later version.
|
||||||
|
|
||||||
To ensure that palace can run without any dependencies outside of the [pip]
|
[The full list of works bundled with palace and other credits][copying]
|
||||||
toolchain, the wheels are bundled with dynamically linked libraries from
|
can be found in our documentation.
|
||||||
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+ |
|
|
||||||
|
|
||||||
[alure]: https://github.com/kcat/alure
|
[alure]: https://github.com/kcat/alure
|
||||||
[OpenAL Soft]: https://kcat.strangesoft.net/openal.html
|
[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/
|
[pip]: https://pip.pypa.io/en/latest/
|
||||||
[PyPI]: https://pypi.org/project/palace/
|
[PyPI]: https://pypi.org/project/palace/
|
||||||
[GH-1]: https://github.com/McSinyx/palace/issues/1
|
[GH-1]: https://github.com/McSinyx/palace/issues/1
|
||||||
[GH-63]: https://github.com/McSinyx/palace/issues/63
|
|
||||||
[CMake]: https://cmake.org/
|
[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
|
[LGPLv3+]: https://www.gnu.org/licenses/lgpl-3.0.en.html
|
||||||
[Vorbis]: https://xiph.org/vorbis/
|
[copying]: https://mcsinyx.github.io/palace/copying.html
|
||||||
[Opus]: http://opus-codec.org/
|
|
||||||
[libsndfile]: http://www.mega-nerd.com/libsndfile/
|
|
||||||
|
|
|
@ -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
|
CHUNK_LEN: int = 12000
|
||||||
QUEUE_SIZE: int = 4
|
QUEUE_SIZE: int = 4
|
||||||
PERIOD: float = 0.01
|
PERIOD: float = 0.025
|
||||||
|
|
||||||
|
|
||||||
def play(files: Iterable[str], device: str) -> None:
|
def play(files: Iterable[str], device: str) -> None:
|
||||||
|
|
|
@ -23,7 +23,8 @@ from sys import stderr
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Iterable
|
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
|
CHUNK_LEN: int = 12000
|
||||||
QUEUE_SIZE: int = 4
|
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."""
|
"""Load and play files on the given device."""
|
||||||
with Device(device) as dev, Context(dev) as ctx:
|
with Device(device) as dev, Context(dev) as ctx:
|
||||||
print('Opened', dev.name)
|
print('Opened', dev.name)
|
||||||
with Source() as src, Effect() as fx:
|
print('Loading reverb preset', reverb)
|
||||||
print('Loading reverb preset', reverb)
|
with Source() as src, ReverbEffect(reverb) as fx:
|
||||||
fx.reverb_preset = reverb
|
|
||||||
src.sends[0].effect = fx
|
src.sends[0].effect = fx
|
||||||
|
|
||||||
for filename in files:
|
for filename in files:
|
||||||
try:
|
try:
|
||||||
decoder = decode(filename)
|
decoder = decode(filename)
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ['setuptools>=43', 'wheel>=0.31', 'Cython']
|
requires = ['setuptools>=43', 'wheel>=0.31', 'Cython']
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = 'setuptools.build_meta'
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = palace
|
name = palace
|
||||||
version = 0.1.3
|
version = 0.2.2
|
||||||
url = https://github.com/McSinyx/palace
|
url = https://mcsinyx.github.io/palace
|
||||||
author = Nguyễn Gia Phong
|
author = Nguyễn Gia Phong
|
||||||
author_email = mcsinyx@disroot.org
|
author_email = mcsinyx@disroot.org
|
||||||
classifiers =
|
classifiers =
|
||||||
Development Status :: 3 - Alpha
|
Development Status :: 4 - Beta
|
||||||
Intended Audience :: Developers
|
Intended Audience :: Developers
|
||||||
License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
|
License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
|
||||||
|
Operating System :: MacOS
|
||||||
Operating System :: POSIX :: Linux
|
Operating System :: POSIX :: Linux
|
||||||
Programming Language :: C++
|
Programming Language :: C++
|
||||||
Programming Language :: Cython
|
Programming Language :: Cython
|
||||||
|
@ -19,7 +20,7 @@ classifiers =
|
||||||
Topic :: Software Development :: Libraries
|
Topic :: Software Development :: Libraries
|
||||||
Typing :: Typed
|
Typing :: Typed
|
||||||
license = LGPLv3+
|
license = LGPLv3+
|
||||||
license_file = LICENSE
|
license_files = LICENSE
|
||||||
description = Pythonic Audio Library and Codecs Environment
|
description = Pythonic Audio Library and Codecs Environment
|
||||||
long_description = file: README.md
|
long_description = file: README.md
|
||||||
long_description_content_type = text/markdown
|
long_description_content_type = text/markdown
|
||||||
|
|
18
setup.py
18
setup.py
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# setup script
|
# setup script
|
||||||
# Copyright (C) 2019, 2020 Nguyễn Gia Phong
|
# Copyright (C) 2019, 2020 Nguyễn Gia Phong
|
||||||
|
# Copyright (C) 2020 Francesco Caliumi
|
||||||
#
|
#
|
||||||
# This file is part of palace.
|
# This file is part of palace.
|
||||||
#
|
#
|
||||||
|
@ -21,13 +22,13 @@ import re
|
||||||
from distutils import log
|
from distutils import log
|
||||||
from distutils.command.clean import clean
|
from distutils.command.clean import clean
|
||||||
from distutils.dir_util import mkpath
|
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 distutils.file_util import copy_file
|
||||||
from operator import methodcaller
|
from operator import methodcaller
|
||||||
from os import environ, unlink
|
from os import environ, unlink
|
||||||
from os.path import dirname, join
|
from os.path import dirname, join
|
||||||
from platform import system
|
from platform import system
|
||||||
from subprocess import DEVNULL, PIPE, run
|
from subprocess import DEVNULL, PIPE, run, CalledProcessError
|
||||||
|
|
||||||
from Cython.Build import cythonize
|
from Cython.Build import cythonize
|
||||||
from setuptools import setup, Extension
|
from setuptools import setup, Extension
|
||||||
|
@ -55,10 +56,15 @@ class BuildAlure2Ext(build_ext):
|
||||||
"""
|
"""
|
||||||
super().finalize_options()
|
super().finalize_options()
|
||||||
mkpath(self.build_temp)
|
mkpath(self.build_temp)
|
||||||
copy_file(join(dirname(__file__), 'CMakeLists.txt'),
|
copy_file(join(dirname(__file__), 'CMakeLists.txt'), self.build_temp)
|
||||||
self.build_temp)
|
try:
|
||||||
cmake = run(['cmake', '.'], check=True, stdout=DEVNULL, stderr=PIPE,
|
cmake = run(
|
||||||
cwd=self.build_temp, universal_newlines=True)
|
['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'),
|
for key, value in map(methodcaller('groups'),
|
||||||
re.finditer(r'^alure2_(\w*)=(.*)$',
|
re.finditer(r'^alure2_(\w*)=(.*)$',
|
||||||
cmake.stderr, re.MULTILINE)):
|
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.utility cimport pair
|
||||||
from libcpp.vector cimport vector
|
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
|
# OpenAL and Alure auxiliary declarations
|
||||||
|
@ -84,37 +84,37 @@ cdef extern from 'alure2-typeviews.h' namespace 'alure' nogil:
|
||||||
# Alure main module
|
# Alure main module
|
||||||
cdef extern from 'alure2.h' nogil:
|
cdef extern from 'alure2.h' nogil:
|
||||||
cdef cppclass EFXEAXREVERBPROPERTIES:
|
cdef cppclass EFXEAXREVERBPROPERTIES:
|
||||||
float flDensity
|
float density 'flDensity'
|
||||||
float flDiffusion
|
float diffusion 'flDiffusion'
|
||||||
float flGain
|
float gain 'flGain'
|
||||||
float flGainHF
|
float gain_hf 'flGainHF'
|
||||||
float flGainLF
|
float gain_lf 'flGainLF'
|
||||||
float flDecayTime
|
float decay_time 'flDecayTime'
|
||||||
float flDecayHFRatio
|
float decay_hf_ratio 'flDecayHFRatio'
|
||||||
float flDecayLFRatio
|
float decay_lf_ratio 'flDecayLFRatio'
|
||||||
float flReflectionsGain
|
float reflections_gain 'flReflectionsGain'
|
||||||
float flReflectionsDelay
|
float reflections_delay 'flReflectionsDelay'
|
||||||
float flReflectionsPan[3]
|
float reflections_pan 'flReflectionsPan'[3]
|
||||||
float flLateReverbGain
|
float late_reverb_gain 'flLateReverbGain'
|
||||||
float flLateReverbDelay
|
float late_reverb_delay 'flLateReverbDelay'
|
||||||
float flLateReverbPan[3]
|
float late_reverb_pan 'flLateReverbPan'[3]
|
||||||
float flEchoTime
|
float echo_time 'flEchoTime'
|
||||||
float flEchoDepth
|
float echo_depth 'flEchoDepth'
|
||||||
float flModulationTime
|
float modulation_time 'flModulationTime'
|
||||||
float flModulationDepth
|
float modulation_depth 'flModulationDepth'
|
||||||
float flAirAbsorptionGainHF
|
float air_absorption_gain_hf 'flAirAbsorptionGainHF'
|
||||||
float flHFReference
|
float hf_reference 'flHFReference'
|
||||||
float flLFReference
|
float lf_reference 'flLFReference'
|
||||||
float flRoomRolloffFactor
|
float room_rolloff_factor 'flRoomRolloffFactor'
|
||||||
int iDecayHFLimit
|
bint decay_hf_limit 'iDecayHFLimit'
|
||||||
|
|
||||||
cdef cppclass EFXCHORUSPROPERTIES:
|
cdef cppclass EFXCHORUSPROPERTIES:
|
||||||
int iWaveform
|
bint waveform 'iWaveform'
|
||||||
int iPhase
|
int phase 'iPhase'
|
||||||
float flRate
|
float rate 'flRate'
|
||||||
float flDepth
|
float depth 'flDepth'
|
||||||
float flFeedback
|
float feedback 'flFeedback'
|
||||||
float flDelay
|
float delay 'flDelay'
|
||||||
|
|
||||||
|
|
||||||
cdef extern from 'alure2.h' namespace 'alure' nogil:
|
cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||||
|
@ -129,7 +129,6 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||||
# String: string
|
# String: string
|
||||||
# StringView: string
|
# StringView: string
|
||||||
# SharedPtr: shared_ptr
|
# SharedPtr: shared_ptr
|
||||||
# SharedFuture: shared_future
|
|
||||||
|
|
||||||
# Structs:
|
# Structs:
|
||||||
cdef cppclass AttributePair:
|
cdef cppclass AttributePair:
|
||||||
|
@ -142,9 +141,9 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||||
unsigned send 'mSend'
|
unsigned send 'mSend'
|
||||||
|
|
||||||
# Enum classes:
|
# Enum classes:
|
||||||
cdef enum SampleType:
|
ctypedef enum SampleType:
|
||||||
pass
|
pass
|
||||||
cdef enum ChannelConfig:
|
ctypedef enum ChannelConfig:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# The following relies on C++ implicit conversion from char* to string.
|
# 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 frames_to_bytes 'FramesToBytes'(unsigned, ChannelConfig, SampleType) except +
|
||||||
cdef unsigned bytes_to_frames 'BytesToFrames'(unsigned, ChannelConfig, SampleType)
|
cdef unsigned bytes_to_frames 'BytesToFrames'(unsigned, ChannelConfig, SampleType)
|
||||||
|
|
||||||
cdef enum DeviceEnumeration:
|
ctypedef enum DeviceEnumeration:
|
||||||
Basic 'alure::DeviceEnumeration::Basic'
|
Basic 'alure::DeviceEnumeration::Basic'
|
||||||
Full 'alure::DeviceEnumeration::Full'
|
Full 'alure::DeviceEnumeration::Full'
|
||||||
Capture 'alure::DeviceEnumeration::Capture'
|
Capture 'alure::DeviceEnumeration::Capture'
|
||||||
|
|
||||||
cdef enum DefaultDeviceType:
|
ctypedef enum DefaultDeviceType:
|
||||||
Basic 'alure::DefaultDeviceType::Basic'
|
Basic 'alure::DefaultDeviceType::Basic'
|
||||||
Full 'alure::DefaultDeviceType::Full'
|
Full 'alure::DefaultDeviceType::Full'
|
||||||
Capture 'alure::DefaultDeviceType::Capture'
|
Capture 'alure::DefaultDeviceType::Capture'
|
||||||
|
|
||||||
cdef enum PlaybackName:
|
ctypedef enum PlaybackName:
|
||||||
Basic 'alure::PlaybackName::Basic'
|
Basic 'alure::PlaybackName::Basic'
|
||||||
Full 'alure::PlaybackName::Full'
|
Full 'alure::PlaybackName::Full'
|
||||||
|
|
||||||
cdef enum DistanceModel:
|
ctypedef enum DistanceModel:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
cdef enum Spatialize:
|
ctypedef enum Spatialize:
|
||||||
Off 'alure::Spatialize::Off'
|
Off 'alure::Spatialize::Off'
|
||||||
On 'alure::Spatialize::On'
|
On 'alure::Spatialize::On'
|
||||||
Auto 'alure::Spatialize::Auto'
|
Auto 'alure::Spatialize::Auto'
|
||||||
|
@ -209,48 +208,28 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||||
cdef cppclass DeviceManager:
|
cdef cppclass DeviceManager:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
DeviceManager get_instance 'getInstance'() except +
|
DeviceManager get_instance 'getInstance'() except +
|
||||||
|
|
||||||
DeviceManager() # nil
|
DeviceManager() # nil
|
||||||
DeviceManager(const DeviceManager&)
|
|
||||||
DeviceManager(DeviceManager&&)
|
|
||||||
|
|
||||||
boolean operator bool()
|
boolean operator bool()
|
||||||
|
|
||||||
boolean query_extension 'queryExtension'(const string&) except +
|
boolean query_extension 'queryExtension'(const string&) except +
|
||||||
|
|
||||||
vector[string] enumerate(DeviceEnumeration) except +
|
vector[string] enumerate(DeviceEnumeration) except +
|
||||||
string default_device_name 'defaultDeviceName'(DefaultDeviceType) except +
|
string default_device_name 'defaultDeviceName'(DefaultDeviceType) except +
|
||||||
|
|
||||||
Device open_playback 'openPlayback'() except +
|
|
||||||
Device open_playback 'openPlayback'(const string&) except +
|
Device open_playback 'openPlayback'(const string&) except +
|
||||||
|
|
||||||
cdef cppclass Device:
|
cdef cppclass Device:
|
||||||
ctypedef DeviceImpl* handle_type
|
|
||||||
|
|
||||||
Device() # nil
|
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>=(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()
|
boolean operator bool()
|
||||||
|
|
||||||
handle_type get_handle 'getHandle'()
|
|
||||||
|
|
||||||
string get_name 'getName'() except +
|
string get_name 'getName'() except +
|
||||||
string get_name 'getName'(PlaybackName) except +
|
string get_name 'getName'(PlaybackName) except +
|
||||||
|
|
||||||
boolean query_extension 'queryExtension'(const string&) except +
|
boolean query_extension 'queryExtension'(const string&) except +
|
||||||
|
|
||||||
Version get_alc_version 'getALCVersion'() except +
|
Version get_alc_version 'getALCVersion'() except +
|
||||||
Version get_efx_version 'getEFXVersion'() except +
|
Version get_efx_version 'getEFXVersion'() except +
|
||||||
|
|
||||||
|
@ -274,34 +253,24 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||||
void close() except +
|
void close() except +
|
||||||
|
|
||||||
cdef cppclass Context:
|
cdef cppclass Context:
|
||||||
ctypedef ContextImpl* handle_type
|
|
||||||
|
|
||||||
Context() # nil
|
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>=(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()
|
boolean operator bool()
|
||||||
|
|
||||||
handle_type get_handle 'getHandle'()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
void make_current 'MakeCurrent'(Context) except +
|
void make_current 'MakeCurrent'(Context) except +
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
Context get_current 'GetCurrent'() except +
|
Context get_current 'GetCurrent'() except +
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
void make_thread_current 'MakeThreadCurrent'(Context) except +
|
void make_thread_current 'MakeThreadCurrent'(Context) except +
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
Context get_thread_current 'GetThreadCurrent'() except +
|
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 +
|
int get_default_resampler_index 'getDefaultResamplerIndex'() except +
|
||||||
|
|
||||||
void precache_buffers_async 'precacheBuffersAsync'(vector[StringView]) except +
|
void precache_buffers_async 'precacheBuffersAsync'(vector[StringView]) except +
|
||||||
|
|
||||||
Buffer create_buffer_from 'createBufferFrom'(string, shared_ptr[Decoder]) 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 +
|
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'(string) except +
|
||||||
void remove_buffer 'removeBuffer'(Buffer) except +
|
void remove_buffer 'removeBuffer'(Buffer) except +
|
||||||
|
|
||||||
|
@ -350,63 +314,25 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||||
void update() except +
|
void update() except +
|
||||||
|
|
||||||
cdef cppclass Listener:
|
cdef cppclass Listener:
|
||||||
ctypedef ListenerImpl* handle_type
|
|
||||||
|
|
||||||
Listener() # nil
|
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()
|
boolean operator bool()
|
||||||
|
|
||||||
handle_type get_handle 'getHandle'()
|
|
||||||
|
|
||||||
float set_gain 'setGain'(float) except +
|
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 Vector3 &) except +
|
||||||
void set_position 'setPosition'(const float*) except +
|
|
||||||
|
|
||||||
void set_velocity 'setVelocity'(const Vector3&) 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 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 +
|
void set_meters_per_unit 'setMetersPerUnit'(float) except +
|
||||||
|
|
||||||
cdef cppclass Buffer:
|
cdef cppclass Buffer:
|
||||||
ctypedef BufferImpl* handle_type
|
|
||||||
|
|
||||||
Buffer() # nil
|
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>=(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()
|
boolean operator bool()
|
||||||
|
|
||||||
handle_type get_handle 'getHandle'()
|
|
||||||
|
|
||||||
unsigned get_length 'getLength'() except +
|
unsigned get_length 'getLength'() except +
|
||||||
unsigned get_frequency 'getFrequency'() except +
|
unsigned get_frequency 'getFrequency'() except +
|
||||||
ChannelConfig get_channel_config 'getChannelConfig'() except +
|
ChannelConfig get_channel_config 'getChannelConfig'() except +
|
||||||
|
@ -414,45 +340,29 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||||
unsigned get_size 'getSize'() except +
|
unsigned get_size 'getSize'() except +
|
||||||
size_t get_source_count 'getSourceCount'() except +
|
size_t get_source_count 'getSourceCount'() except +
|
||||||
vector[Source] get_sources 'getSources'() 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 +
|
pair[unsigned, unsigned] get_loop_points 'getLoopPoints'() except +
|
||||||
void set_loop_points 'setLoopPoints'(unsigned, unsigned) except +
|
void set_loop_points 'setLoopPoints'(unsigned, unsigned) except +
|
||||||
|
|
||||||
cdef cppclass Source:
|
cdef cppclass Source:
|
||||||
ctypedef SourceImpl* handle_type
|
|
||||||
|
|
||||||
Source() # nil
|
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>=(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()
|
boolean operator bool()
|
||||||
|
|
||||||
handle_type get_handle 'getHandle'()
|
|
||||||
|
|
||||||
void play(Buffer) except +
|
void play(Buffer) except +
|
||||||
void play(shared_ptr[Decoder], int, int) except +
|
void play(shared_ptr[Decoder], int, int) except +
|
||||||
void play(shared_future[Buffer]) except +
|
|
||||||
|
|
||||||
void stop() except +
|
void stop() except +
|
||||||
void fade_out_to_stop 'fadeOutToStop'(float, milliseconds) except +
|
void fade_out_to_stop 'fadeOutToStop'(float, milliseconds) except +
|
||||||
void pause() except +
|
void pause() except +
|
||||||
void resume() except +
|
void resume() except +
|
||||||
|
|
||||||
boolean is_pending 'isPending'() except +
|
|
||||||
boolean is_playing 'isPlaying'() except +
|
boolean is_playing 'isPlaying'() except +
|
||||||
boolean is_paused 'isPaused'() except +
|
boolean is_paused 'isPaused'() except +
|
||||||
boolean is_playing_or_pending 'isPlayingOrPending'() except +
|
|
||||||
|
|
||||||
void set_group 'setGroup'(SourceGroup) except +
|
void set_group 'setGroup'(SourceGroup) except +
|
||||||
SourceGroup get_group 'getGroup'() except +
|
SourceGroup get_group 'getGroup'() except +
|
||||||
|
@ -476,50 +386,30 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||||
float get_gain 'getGain'() except +
|
float get_gain 'getGain'() except +
|
||||||
void set_gain_range 'setGainRange'(float, float) except +
|
void set_gain_range 'setGainRange'(float, float) except +
|
||||||
pair[float, float] get_gain_range 'getGainRange'() 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 +
|
void set_distance_range 'setDistanceRange'(float, float) except +
|
||||||
pair[float, float] get_distance_range 'getDistanceRange'() 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 Vector3&) except +
|
||||||
void set_position 'setPosition'(const float*) except +
|
|
||||||
Vector3 get_position 'getPosition'() except +
|
Vector3 get_position 'getPosition'() except +
|
||||||
|
|
||||||
void set_velocity 'setVelocity'(const Vector3&) except +
|
void set_velocity 'setVelocity'(const Vector3&) except +
|
||||||
void set_velocity 'setVelocity'(const float*) except +
|
|
||||||
Vector3 get_velocity 'getVelocity'() except +
|
Vector3 get_velocity 'getVelocity'() except +
|
||||||
|
|
||||||
void set_direction 'setDirection'(const Vector3&) except +
|
void set_direction 'setDirection'(const Vector3&) except +
|
||||||
void set_direction 'setDirection'(const float*) except +
|
|
||||||
Vector3 get_direction 'getDirection'() except +
|
Vector3 get_direction 'getDirection'() except +
|
||||||
|
|
||||||
void set_orientation 'setOrientation'(const pair[Vector3, Vector3]&) 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 +
|
pair[Vector3, Vector3] get_orientation 'getOrientation'() except +
|
||||||
|
|
||||||
void set_cone_angles 'setConeAngles'(float, float) except +
|
void set_cone_angles 'setConeAngles'(float, float) except +
|
||||||
pair[float, float] get_cone_angles 'getConeAngles'() 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 +
|
void set_outer_cone_gains 'setOuterConeGains'(float, float) except +
|
||||||
pair[float, float] get_outer_cone_gains 'getOuterConeGains'() 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 +
|
void set_rolloff_factors 'setRolloffFactors'(float, float) except +
|
||||||
pair[float, float] get_rolloff_factors 'getRolloffFactors'() 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 +
|
void set_doppler_factor 'setDopplerFactor'(float) except +
|
||||||
float get_doppler_factor 'getDopplerFactor'() except +
|
float get_doppler_factor 'getDopplerFactor'() except +
|
||||||
|
@ -556,26 +446,15 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||||
void destroy() except +
|
void destroy() except +
|
||||||
|
|
||||||
cdef cppclass SourceGroup:
|
cdef cppclass SourceGroup:
|
||||||
ctypedef SourceImpl* handle_type
|
|
||||||
|
|
||||||
SourceGroup() # nil
|
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>=(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()
|
boolean operator bool()
|
||||||
|
|
||||||
handle_type get_handle 'getHandle'()
|
|
||||||
void set_parent_group 'setParentGroup'(SourceGroup) except +
|
void set_parent_group 'setParentGroup'(SourceGroup) except +
|
||||||
SourceGroup get_parent_group 'getParentGroup'() except +
|
SourceGroup get_parent_group 'getParentGroup'() except +
|
||||||
|
|
||||||
|
@ -595,27 +474,15 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||||
void destroy() except +
|
void destroy() except +
|
||||||
|
|
||||||
cdef cppclass AuxiliaryEffectSlot:
|
cdef cppclass AuxiliaryEffectSlot:
|
||||||
ctypedef AuxiliaryEffectSlotImpl* handle_type
|
|
||||||
|
|
||||||
AuxiliaryEffectSlot() # nil
|
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>=(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()
|
boolean operator bool()
|
||||||
|
|
||||||
handle_type get_handle 'getHandle'()
|
|
||||||
|
|
||||||
void set_gain 'setGain'(float) except +
|
void set_gain 'setGain'(float) except +
|
||||||
void set_send_auto 'setSendAuto'(bool) except +
|
void set_send_auto 'setSendAuto'(bool) except +
|
||||||
void apply_effect 'applyEffect'(Effect) 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 +
|
size_t get_use_count 'getUseCount'() except +
|
||||||
|
|
||||||
cdef cppclass Effect:
|
cdef cppclass Effect:
|
||||||
ctypedef EffectImpl* handle_type
|
|
||||||
|
|
||||||
Effect() # nil
|
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>=(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()
|
boolean operator bool()
|
||||||
|
|
||||||
handle_type get_handle 'getHandle'()
|
|
||||||
|
|
||||||
void set_reverb_properties 'setReverbProperties'(const EFXEAXREVERBPROPERTIES&) except +
|
void set_reverb_properties 'setReverbProperties'(const EFXEAXREVERBPROPERTIES&) except +
|
||||||
void set_chorus_properties 'setChorusProperties'(const EFXCHORUSPROPERTIES&) except +
|
void set_chorus_properties 'setChorusProperties'(const EFXCHORUSPROPERTIES&) except +
|
||||||
|
|
||||||
void destroy() except +
|
void destroy() except +
|
||||||
|
|
||||||
cdef cppclass Decoder:
|
cdef cppclass Decoder:
|
||||||
int get_frequency 'getFrequency'()
|
int get_frequency 'getFrequency'()
|
||||||
ChannelConfig get_channel_config 'getChannelConfig'()
|
ChannelConfig get_channel_config 'getChannelConfig'()
|
||||||
SampleType get_sample_type 'getSampleType'()
|
SampleType get_sample_type 'getSampleType'()
|
||||||
|
|
||||||
uint64_t get_length 'getLength'()
|
uint64_t get_length 'getLength'()
|
||||||
boolean seek(uint64_t)
|
boolean seek(uint64_t)
|
||||||
|
|
||||||
pair[uint64_t, uint64_t] get_loop_points 'getLoopPoints'()
|
pair[uint64_t, uint64_t] get_loop_points 'getLoopPoints'()
|
||||||
|
|
||||||
int read(void*, int)
|
int read(void*, int)
|
||||||
|
|
||||||
cdef cppclass DecoderFactory:
|
cdef cppclass DecoderFactory:
|
||||||
|
@ -669,6 +520,7 @@ cdef extern from 'alure2.h' namespace 'alure' nogil:
|
||||||
cdef cppclass FileIOFactory:
|
cdef cppclass FileIOFactory:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
unique_ptr[FileIOFactory] set(unique_ptr[FileIOFactory])
|
unique_ptr[FileIOFactory] set(unique_ptr[FileIOFactory])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
FileIOFactory& get()
|
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 +
|
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 extern from '<ratio>' namespace 'std' nogil:
|
||||||
cdef cppclass nano:
|
cdef cppclass nano:
|
||||||
pass
|
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
|
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||||
#
|
#
|
||||||
# This file is part of palace.
|
# This file is part of palace.
|
||||||
|
@ -16,23 +17,38 @@
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with palace. If not, see <https://www.gnu.org/licenses/>.
|
# along with palace. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""This module provide default objects of palace classes as fixtures
|
from os.path import abspath, dirname, join
|
||||||
for convenient testing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pytest import fixture
|
from pytest import fixture
|
||||||
from palace import Device, Context
|
|
||||||
|
DATA_DIR = abspath(join(dirname(__file__), 'data'))
|
||||||
|
|
||||||
|
|
||||||
@fixture(scope='session')
|
@fixture
|
||||||
def device():
|
def aiff():
|
||||||
"""Provide the default device."""
|
"""Provide a sample AIFF file."""
|
||||||
with Device() as dev: yield dev
|
return join(DATA_DIR, '24741__tim-kahn__b23-c1-raw.aiff')
|
||||||
|
|
||||||
|
|
||||||
@fixture(scope='session')
|
@fixture
|
||||||
def context(device):
|
def flac():
|
||||||
"""Provide a context creared from the default device
|
"""Provide a sample FLAC file."""
|
||||||
(default context).
|
return join(DATA_DIR, '261590__kwahmah-02__little-glitch.flac')
|
||||||
"""
|
|
||||||
with Context(device) as ctx: yield ctx
|
|
||||||
|
@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
|
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||||
#
|
#
|
||||||
# This file is part of palace.
|
# This file is part of palace.
|
||||||
|
@ -22,7 +22,7 @@ for single-precision floating-point numbers.
|
||||||
__all__ = ['FLT_MAX', 'allclose', 'isclose']
|
__all__ = ['FLT_MAX', 'allclose', 'isclose']
|
||||||
|
|
||||||
from math import isclose as _isclose
|
from math import isclose as _isclose
|
||||||
from typing import Sequence
|
from typing import Any, Callable, Sequence
|
||||||
|
|
||||||
FLT_EPSILON: float = 2.0 ** -23
|
FLT_EPSILON: float = 2.0 ** -23
|
||||||
FLT_MAX: float = 2.0**128 - 2.0**104
|
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)
|
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
|
"""Determine whether two sequences of single-precision
|
||||||
floating-point numbers are close in value.
|
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.
|
That is, NaN is not close to anything, even itself.
|
||||||
inf and -inf are only close to themselves.
|
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 Ngô Ngọc Đức Huy
|
||||||
# Copyright (C) 2020 Nguyễn Gia Phong
|
# Copyright (C) 2020 Nguyễn Gia Phong
|
||||||
# Copyright (C) 2020 Ngô Xuân Minh
|
# Copyright (C) 2020 Ngô Xuân Minh
|
||||||
|
@ -26,21 +26,29 @@ from pytest import raises
|
||||||
from math import inf
|
from math import inf
|
||||||
|
|
||||||
|
|
||||||
def test_with_context(device):
|
def test_comparison(device):
|
||||||
"""Test if `with` can be used to start a context
|
"""Test basic comparisons."""
|
||||||
and is destroyed properly.
|
with Context(device) as c0, Context(device) as c1, Context(device) as c2:
|
||||||
"""
|
assert c0 != c1
|
||||||
with Context(device) as context:
|
contexts = [c1, c1, c0, c2]
|
||||||
assert current_context() == context
|
contexts.sort()
|
||||||
|
contexts.remove(c2)
|
||||||
|
contexts.remove(c0)
|
||||||
|
assert contexts[0] == contexts[1]
|
||||||
|
|
||||||
|
|
||||||
def test_nested_context_manager(device):
|
def test_bool(device):
|
||||||
"""Test if the context manager returns to the
|
"""Test boolean value."""
|
||||||
previous context.
|
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) as context:
|
||||||
with Context(device): pass
|
# At the moment these are no-op.
|
||||||
assert current_context() == context
|
context.start_batch()
|
||||||
|
context.end_batch()
|
||||||
|
|
||||||
|
|
||||||
def test_message_handler(device):
|
def test_message_handler(device):
|
||||||
|
@ -61,36 +69,48 @@ def test_async_wake_interval(device):
|
||||||
assert context.async_wake_interval == 42
|
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):
|
def test_default_resampler_index(device):
|
||||||
"""Test return values default_resampler_index."""
|
"""Test read-only property default_resampler_index."""
|
||||||
with Context(device) as context:
|
with Context(device) as context:
|
||||||
index = context.default_resampler_index
|
index = context.default_resampler_index
|
||||||
assert index >= 0
|
assert index >= 0
|
||||||
assert len(context.available_resamplers) > index
|
assert len(context.available_resamplers) > index
|
||||||
|
with raises(AttributeError): context.available_resamplers = 0
|
||||||
|
|
||||||
|
|
||||||
def test_doppler_factor(device):
|
def test_doppler_factor(device):
|
||||||
"""Test write property doppler_factor."""
|
"""Test write-only property doppler_factor."""
|
||||||
with Context(device) as context:
|
with Context(device) as context:
|
||||||
context.doppler_factor = 4/9
|
context.doppler_factor = 4/9
|
||||||
context.doppler_factor = 9/4
|
context.doppler_factor = 9/4
|
||||||
context.doppler_factor = 0
|
context.doppler_factor = 0
|
||||||
context.doppler_factor = inf
|
context.doppler_factor = inf
|
||||||
with raises(ValueError): context.doppler_factor = -1
|
with raises(ValueError): context.doppler_factor = -1
|
||||||
|
with raises(AttributeError): context.doppler_factor
|
||||||
|
|
||||||
|
|
||||||
def test_speed_of_sound(device):
|
def test_speed_of_sound(device):
|
||||||
"""Test write property speed_of_sound."""
|
"""Test write-only property speed_of_sound."""
|
||||||
with Context(device) as context:
|
with Context(device) as context:
|
||||||
context.speed_of_sound = 5/7
|
context.speed_of_sound = 5/7
|
||||||
context.speed_of_sound = 7/5
|
context.speed_of_sound = 7/5
|
||||||
with raises(ValueError): context.speed_of_sound = 0
|
with raises(ValueError): context.speed_of_sound = 0
|
||||||
context.speed_of_sound = inf
|
context.speed_of_sound = inf
|
||||||
with raises(ValueError): context.speed_of_sound = -1
|
with raises(ValueError): context.speed_of_sound = -1
|
||||||
|
with raises(AttributeError): context.speed_of_sound
|
||||||
|
|
||||||
|
|
||||||
def test_distance_model(device):
|
def test_distance_model(device):
|
||||||
"""Test preset values distance_model."""
|
"""Test write-only distance_model."""
|
||||||
with Context(device) as context:
|
with Context(device) as context:
|
||||||
for model in distance_models: context.distance_model = model
|
for model in distance_models: context.distance_model = model
|
||||||
with raises(ValueError): context.distance_model = 'EYYYYLMAO'
|
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."""
|
"""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 math import inf, pi
|
||||||
from operator import is_
|
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 pytest import raises
|
||||||
|
|
||||||
from fmath import FLT_MAX, allclose, isclose
|
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):
|
def test_group(context):
|
||||||
"""Test read-write property group."""
|
"""Test read-write property group."""
|
||||||
with Source(context) as source, SourceGroup(context) as source_group:
|
with Source(context) as source, SourceGroup(context) as source_group:
|
||||||
|
@ -47,11 +90,39 @@ def test_priority(context):
|
||||||
assert source.priority == 42
|
assert source.priority == 42
|
||||||
|
|
||||||
|
|
||||||
def test_offset(context):
|
def test_offset(context, ogg):
|
||||||
"""Test read-write property offset."""
|
"""Test read-write property offset."""
|
||||||
with Source(context) as source:
|
with Buffer(ogg) as buffer, buffer.play() as source:
|
||||||
assert source.offset == 0
|
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):
|
def test_looping(context):
|
||||||
|
@ -129,9 +200,9 @@ def test_velocity(context):
|
||||||
def test_orientation(context):
|
def test_orientation(context):
|
||||||
"""Test read-write property orientation."""
|
"""Test read-write property orientation."""
|
||||||
with Source(context) as source:
|
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)
|
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):
|
def test_cone_angles(context):
|
||||||
|
@ -220,8 +291,8 @@ def test_spatialize(context):
|
||||||
|
|
||||||
def test_resampler_index(context):
|
def test_resampler_index(context):
|
||||||
"""Test read-write property resampler_index."""
|
"""Test read-write property resampler_index."""
|
||||||
with Source(context) as source:
|
with Source() as source:
|
||||||
# TODO: test initial value
|
assert source.resampler_index == context.default_resampler_index
|
||||||
with raises(ValueError): source.resampler_index = -1
|
with raises(ValueError): source.resampler_index = -1
|
||||||
source.resampler_index = 69
|
source.resampler_index = 69
|
||||||
assert 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)):
|
for gain_auto in product(*repeat((False, True), 3)):
|
||||||
source.gain_auto = gain_auto
|
source.gain_auto = gain_auto
|
||||||
assert all(map(is_, 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]
|
[tox]
|
||||||
envlist = py
|
envlist = py
|
||||||
minversion = 3.3
|
minversion = 3.3
|
||||||
isolated_build = true
|
isolated_build = True
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
setenv =
|
|
||||||
CYTHON_TRACE = 1
|
|
||||||
deps =
|
deps =
|
||||||
flake8
|
|
||||||
Cython
|
Cython
|
||||||
|
scipy
|
||||||
pytest-cov
|
pytest-cov
|
||||||
commands =
|
commands = pytest
|
||||||
flake8
|
setenv = CYTHON_TRACE = 1
|
||||||
pytest
|
passenv = TRAVIS
|
||||||
|
|
||||||
|
[testenv:lint]
|
||||||
|
skip_install = true
|
||||||
|
deps = flake8
|
||||||
|
commands = flake8
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
filename = *.pxd, *.pyx, *.py
|
filename = *.pxd, *.pyx, *.py
|
||||||
|
|
Loading…
Reference in New Issue