palace
Advanced tools
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| # 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)) |
| # 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 |
| # 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) |
| # 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 |
| # Single-precision floating-point math | ||
| # 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 provides access to mathematical functions | ||
| for single-precision floating-point numbers. | ||
| """ | ||
| __all__ = ['FLT_MAX', 'allclose', 'isclose'] | ||
| from math import isclose as _isclose | ||
| from typing import Any, Callable, Sequence | ||
| FLT_EPSILON: float = 2.0 ** -23 | ||
| FLT_MAX: float = 2.0**128 - 2.0**104 | ||
| def isclose(a: float, b: float) -> bool: | ||
| """Determine whether two single-precision floating-point numbers | ||
| are close in value. | ||
| For the values to be considered close, the relative difference | ||
| between them must be smaller than FLT_EPSILON. | ||
| -inf, inf and NaN behave similarly to the IEEE 754 Standard. | ||
| That is, NaN is not close to anything, even itself. | ||
| inf and -inf are only close to themselves. | ||
| """ | ||
| return _isclose(a, b, rel_tol=FLT_EPSILON) | ||
| def allclose(a: Sequence[float], b: Sequence[float], | ||
| close: Callable[[Any, Any], bool] = isclose) -> bool: | ||
| """Determine whether two sequences of single-precision | ||
| floating-point numbers are close in value. | ||
| For the values to be considered close, the relative difference | ||
| between them must be smaller than FLT_EPSILON. | ||
| -inf, inf and NaN behave similarly to the IEEE 754 Standard. | ||
| That is, NaN is not close to anything, even itself. | ||
| inf and -inf are only close to themselves. | ||
| """ | ||
| return type(a) is type(b) and all(map(close, a, b)) |
| # Context pytest module | ||
| # Copyright (C) 2020 Ngô Ngọc Đức Huy | ||
| # Copyright (C) 2020 Nguyễn Gia Phong | ||
| # Copyright (C) 2020 Ngô Xuân Minh | ||
| # | ||
| # 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 Context.""" | ||
| from palace import current_context, distance_models, Context, MessageHandler | ||
| from pytest import raises | ||
| from math import inf | ||
| def test_comparison(device): | ||
| """Test basic comparisons.""" | ||
| with Context(device) as c0, Context(device) as c1, Context(device) as c2: | ||
| assert c0 != c1 | ||
| contexts = [c1, c1, c0, c2] | ||
| contexts.sort() | ||
| contexts.remove(c2) | ||
| contexts.remove(c0) | ||
| assert contexts[0] == contexts[1] | ||
| def test_bool(device): | ||
| """Test boolean value.""" | ||
| with Context(device) as context: assert context | ||
| assert not context | ||
| def test_batch_control(device): | ||
| """Test calls of start_batch and end_batch.""" | ||
| with Context(device) as context: | ||
| # At the moment these are no-op. | ||
| context.start_batch() | ||
| context.end_batch() | ||
| def test_message_handler(device): | ||
| """Test read-write property MessageHandler.""" | ||
| context = Context(device) | ||
| assert type(context.message_handler) is MessageHandler | ||
| message_handler_test = type('MessageHandlerTest', (MessageHandler,), {})() | ||
| context.message_handler = message_handler_test | ||
| assert context.message_handler is message_handler_test | ||
| with context: | ||
| assert current_context().message_handler is context.message_handler | ||
| def test_async_wake_interval(device): | ||
| """Test read-write property async_wake_interval.""" | ||
| with Context(device) as context: | ||
| 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): | ||
| """Test read-only property default_resampler_index.""" | ||
| with Context(device) as context: | ||
| index = context.default_resampler_index | ||
| assert index >= 0 | ||
| assert len(context.available_resamplers) > index | ||
| with raises(AttributeError): context.available_resamplers = 0 | ||
| def test_doppler_factor(device): | ||
| """Test write-only property doppler_factor.""" | ||
| with Context(device) as context: | ||
| context.doppler_factor = 4/9 | ||
| context.doppler_factor = 9/4 | ||
| context.doppler_factor = 0 | ||
| context.doppler_factor = inf | ||
| with raises(ValueError): context.doppler_factor = -1 | ||
| with raises(AttributeError): context.doppler_factor | ||
| def test_speed_of_sound(device): | ||
| """Test write-only property speed_of_sound.""" | ||
| with Context(device) as context: | ||
| context.speed_of_sound = 5/7 | ||
| context.speed_of_sound = 7/5 | ||
| with raises(ValueError): context.speed_of_sound = 0 | ||
| context.speed_of_sound = inf | ||
| with raises(ValueError): context.speed_of_sound = -1 | ||
| with raises(AttributeError): context.speed_of_sound | ||
| def test_distance_model(device): | ||
| """Test write-only distance_model.""" | ||
| with Context(device) as context: | ||
| for model in distance_models: context.distance_model = model | ||
| with raises(ValueError): context.distance_model = 'EYYYYLMAO' | ||
| with raises(AttributeError): context.distance_model |
| # 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 |
| # 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 |
| # Source pytest module | ||
| # 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 Source.""" | ||
| from itertools import permutations, product, repeat | ||
| from math import inf, pi | ||
| from operator import is_ | ||
| from random import random, shuffle | ||
| from palace import Buffer, BaseEffect, Source, SourceGroup | ||
| from pytest import raises | ||
| from fmath import FLT_MAX, allclose, isclose | ||
| def test_comparison(context): | ||
| """Test basic comparisons.""" | ||
| with Source() as source0, Source() as source1, Source() as source2: | ||
| assert source0 != source1 | ||
| sources = [source1, source1, source0, source2] | ||
| sources.sort() | ||
| sources.remove(source2) | ||
| sources.remove(source0) | ||
| assert sources[0] == sources[1] | ||
| def test_bool(context): | ||
| """Test boolean value.""" | ||
| with Source() as source: assert source | ||
| assert not source | ||
| def test_control(context, flac): | ||
| """Test calling control methods.""" | ||
| with Buffer(flac) as buffer, buffer.play() as source: | ||
| assert source.playing | ||
| assert not source.paused | ||
| source.pause() | ||
| assert source.paused | ||
| assert not source.playing | ||
| source.resume() | ||
| assert source.playing | ||
| assert not source.paused | ||
| source.stop() | ||
| assert not source.playing | ||
| assert not source.paused | ||
| with raises(AttributeError): source.playing = True | ||
| with raises(AttributeError): source.paused = True | ||
| def test_fade_out_to_stop(context, mp3): | ||
| """Test calling method fade_out_to_stop.""" | ||
| with Buffer(mp3) as buffer, buffer.play() as source: | ||
| source.fade_out_to_stop(5/7, buffer.length>>1) | ||
| with raises(ValueError): source.fade_out_to_stop(0.42, -1) | ||
| def test_group(context): | ||
| """Test read-write property group.""" | ||
| with Source(context) as source, SourceGroup(context) as source_group: | ||
| assert source.group is None | ||
| source.group = source_group | ||
| assert source.group == source_group | ||
| assert source in source_group.sources | ||
| source.group = None | ||
| assert source.group is None | ||
| def test_priority(context): | ||
| """Test read-write property priority.""" | ||
| with Source(context) as source: | ||
| assert source.priority == 0 | ||
| source.priority = 42 | ||
| assert source.priority == 42 | ||
| def test_offset(context, ogg): | ||
| """Test read-write property offset.""" | ||
| with Buffer(ogg) as buffer, buffer.play() as source: | ||
| assert source.offset == 0 | ||
| 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): | ||
| """Test read-write property looping.""" | ||
| with Source(context) as source: | ||
| assert source.looping is False | ||
| source.looping = True | ||
| assert source.looping is True | ||
| source.looping = False | ||
| assert source.looping is False | ||
| def test_pitch(context): | ||
| """Test read-write property pitch.""" | ||
| with Source(context) as source: | ||
| assert isclose(source.pitch, 1) | ||
| with raises(ValueError): source.pitch = -1 | ||
| source.pitch = 5 / 7 | ||
| assert isclose(source.pitch, 5/7) | ||
| def test_gain(context): | ||
| """Test read-write property gain.""" | ||
| with Source(context) as source: | ||
| assert isclose(source.gain, 1) | ||
| with raises(ValueError): source.gain = -1 | ||
| source.gain = 5 / 7 | ||
| assert isclose(source.gain, 5/7) | ||
| def test_gain_range(context): | ||
| """Test read-write property gain_range.""" | ||
| with Source(context) as source: | ||
| assert allclose(source.gain_range, (0, 1)) | ||
| with raises(ValueError): source.gain_range = 9/11, 5/7 | ||
| with raises(ValueError): source.gain_range = 6/9, 420 | ||
| with raises(ValueError): source.gain_range = -420, 6/9 | ||
| source.gain_range = 5/7, 9/11 | ||
| assert allclose(source.gain_range, (5/7, 9/11)) | ||
| def test_distance_range(context): | ||
| """Test read-write property distance_range.""" | ||
| with Source(context) as source: | ||
| assert allclose(source.distance_range, (1, FLT_MAX)) | ||
| with raises(ValueError): source.distance_range = 9/11, 5/7 | ||
| with raises(ValueError): source.distance_range = -420, 6/9 | ||
| with raises(ValueError): source.distance_range = 420, inf | ||
| source.distance_range = 5/7, 9/11 | ||
| assert allclose(source.distance_range, (5/7, 9/11)) | ||
| source.distance_range = 1, FLT_MAX | ||
| assert allclose(source.distance_range, (1, FLT_MAX)) | ||
| def test_position(context): | ||
| """Test read-write property position.""" | ||
| with Source(context) as source: | ||
| assert allclose(source.position, (0, 0, 0)) | ||
| source.position = -1, 0, 1 | ||
| assert allclose(source.position, (-1, 0, 1)) | ||
| source.position = 4, 20, 69 | ||
| assert allclose(source.position, (4, 20, 69)) | ||
| def test_velocity(context): | ||
| """Test read-write property velocity.""" | ||
| with Source(context) as source: | ||
| assert allclose(source.velocity, (0, 0, 0)) | ||
| source.velocity = -1, 0, 1 | ||
| assert allclose(source.velocity, (-1, 0, 1)) | ||
| source.velocity = 4, 20, 69 | ||
| assert allclose(source.velocity, (4, 20, 69)) | ||
| def test_orientation(context): | ||
| """Test read-write property orientation.""" | ||
| with Source(context) as source: | ||
| assert allclose(source.orientation, ((0, 0, -1), (0, 1, 0)), 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): | ||
| """Test read-write property cone_angles.""" | ||
| with Source(context) as source: | ||
| assert allclose(source.cone_angles, (360, 360)) | ||
| with raises(ValueError): source.cone_angles = 420, 69 | ||
| with raises(ValueError): source.cone_angles = -4.20, 69 | ||
| with raises(ValueError): source.cone_angles = 4.20, -69 | ||
| source.cone_angles = 4.20, 69 | ||
| assert allclose(source.cone_angles, (4.20, 69)) | ||
| def test_outer_cone_gains(context): | ||
| """Test read-write property outer_cone_gains.""" | ||
| with Source(context) as source: | ||
| assert allclose(source.outer_cone_gains, (0, 1)) | ||
| with raises(ValueError): source.outer_cone_gains = 6/9, -420 | ||
| with raises(ValueError): source.outer_cone_gains = 6/9, 420 | ||
| with raises(ValueError): source.outer_cone_gains = -420, 6/9 | ||
| with raises(ValueError): source.outer_cone_gains = 420, 6/9 | ||
| source.outer_cone_gains = 5/7, 9/11 | ||
| assert allclose(source.outer_cone_gains, (5/7, 9/11)) | ||
| def test_rolloff_factors(context): | ||
| """Test read-write property rolloff_factors.""" | ||
| with Source(context) as source: | ||
| assert allclose(source.rolloff_factors, (1, 0)) | ||
| with raises(ValueError): source.rolloff_factors = -6, 9 | ||
| with raises(ValueError): source.rolloff_factors = 6, -9 | ||
| source.rolloff_factors = 6, 9 | ||
| assert allclose(source.rolloff_factors, (6, 9)) | ||
| def test_doppler_factor(context): | ||
| """Test read-write property doppler_factor.""" | ||
| with Source(context) as source: | ||
| assert isclose(source.doppler_factor, 1) | ||
| with raises(ValueError): source.doppler_factor = -6.9 | ||
| with raises(ValueError): source.doppler_factor = 4.20 | ||
| source.doppler_factor = 5 / 7 | ||
| assert isclose(source.doppler_factor, 5/7) | ||
| def test_relative(context): | ||
| """Test read-write property relative.""" | ||
| with Source(context) as source: | ||
| assert source.relative is False | ||
| source.relative = True | ||
| assert source.relative is True | ||
| source.relative = False | ||
| assert source.relative is False | ||
| def test_radius(context): | ||
| """Test read-write property radius.""" | ||
| with Source(context) as source: | ||
| assert isclose(source.radius, 0) | ||
| with raises(ValueError): source.radius = -1 | ||
| source.radius = 5 / 7 | ||
| assert isclose(source.radius, 5/7) | ||
| def test_stereo_angles(context): | ||
| """Test read-write property stereo_angles.""" | ||
| with Source(context) as source: | ||
| assert allclose(source.stereo_angles, (pi/6, -pi/6)) | ||
| source.stereo_angles = 420, -69 | ||
| assert allclose(source.stereo_angles, (420, -69)) | ||
| source.stereo_angles = -5/7, 9/11 | ||
| assert allclose(source.stereo_angles, (-5/7, 9/11)) | ||
| def test_spatialize(context): | ||
| """Test read-write property spatialize.""" | ||
| with Source(context) as source: | ||
| assert source.spatialize is None | ||
| source.spatialize = False | ||
| assert source.spatialize is False | ||
| source.spatialize = True | ||
| assert source.spatialize is True | ||
| source.spatialize = None | ||
| assert source.spatialize is None | ||
| def test_resampler_index(context): | ||
| """Test read-write property resampler_index.""" | ||
| with Source() as source: | ||
| assert source.resampler_index == context.default_resampler_index | ||
| with raises(ValueError): source.resampler_index = -1 | ||
| source.resampler_index = 69 | ||
| assert source.resampler_index == 69 | ||
| def test_air_absorption_factor(context): | ||
| """Test read-write property air_absorption_factor.""" | ||
| with Source(context) as source: | ||
| assert isclose(source.air_absorption_factor, 0) | ||
| with raises(ValueError): source.air_absorption_factor = -1 | ||
| with raises(ValueError): source.air_absorption_factor = 11 | ||
| source.air_absorption_factor = 420 / 69 | ||
| assert isclose(source.air_absorption_factor, 420/69) | ||
| def test_gain_auto(context): | ||
| """Test read-write property gain_auto.""" | ||
| with Source(context) as source: | ||
| assert all(gain is True for gain in source.gain_auto) | ||
| for gain_auto in product(*repeat((False, True), 3)): | ||
| source.gain_auto = gain_auto | ||
| assert all(map(is_, source.gain_auto, gain_auto)) | ||
| def tests_sends(device, context): | ||
| """Test send paths assignment.""" | ||
| with Source() as source, BaseEffect() as effect: | ||
| invalid_filter = [-1, 0, 1] | ||
| for i in range(device.max_auxiliary_sends): | ||
| source.sends[i].effect = effect | ||
| source.sends[i].filter = random(), random(), random() | ||
| shuffle(invalid_filter) | ||
| with raises(ValueError): source.sends[i].filter = invalid_filter | ||
| with raises(AttributeError): source.sends[i].effect | ||
| with raises(AttributeError): source.sends[i].filter | ||
| with raises(IndexError): source.sends[-1] | ||
| with raises(TypeError): source.sends[4.2] | ||
| with raises(TypeError): source.sends['0'] | ||
| with raises(TypeError): source.sends[6:9] | ||
| with raises(AttributeError): source.sends = ... | ||
| def test_filter(context): | ||
| """Test write-only property filter.""" | ||
| with Source() as source: | ||
| with raises(AttributeError): source.filter | ||
| source.filter = 1, 6.9, 5/7 | ||
| source.filter = 0, 0, 0 | ||
| for gain, gain_hf, gain_lf in permutations([4, -2, 0]): | ||
| with raises(ValueError): source.filter = gain, gain_hf, gain_lf |
+30
| [tox] | ||
| envlist = py | ||
| minversion = 3.3 | ||
| isolated_build = True | ||
| [testenv] | ||
| deps = | ||
| Cython | ||
| scipy | ||
| flake8 | ||
| pytest-cov | ||
| commands = | ||
| flake8 | ||
| pytest | ||
| setenv = CYTHON_TRACE = 1 | ||
| passenv = TRAVIS | ||
| [flake8] | ||
| filename = *.pxd, *.pyx, *.py | ||
| hang-closing = True | ||
| ignore = W503, E125, E225, E226, E227, E701, E704 | ||
| per-file-ignores = *.pxd:E501,E999 | ||
| ; See https://github.com/PyCQA/pycodestyle/issues/906 | ||
| ;max-doc-length = 72 | ||
| [pytest] | ||
| addopts = --cov=palace | ||
| [coverage:run] | ||
| plugins = Cython.Coverage |
+7
-1
@@ -1,1 +0,7 @@ | ||
| include src/*.pxd src/*.pyx src/*.h tests/*.py examples/*.py CMakeLists.txt | ||
| include CMakeLists.txt | ||
| recursive-include src *.h *.pxd *.pyx | ||
| include tox.ini | ||
| recursive-include tests *.py | ||
| recursive-include examples *.py | ||
| graft tests/data |
| Metadata-Version: 2.1 | ||
| Name: palace | ||
| Version: 0.2.0 | ||
| Version: 0.2.1 | ||
| Summary: Pythonic Audio Library and Codecs Environment | ||
| Home-page: https://github.com/McSinyx/palace | ||
| Home-page: https://mcsinyx.github.io/palace | ||
| Author: Nguyễn Gia Phong | ||
@@ -7,0 +7,0 @@ Author-email: mcsinyx@disroot.org |
@@ -8,3 +8,3 @@ CMakeLists.txt | ||
| setup.py | ||
| ./src/palace.cpp | ||
| tox.ini | ||
| examples/palace-event.py | ||
@@ -24,2 +24,3 @@ examples/palace-hrtf.py | ||
| src/bases.h | ||
| src/palace.cpp | ||
| src/palace.pyx | ||
@@ -29,2 +30,16 @@ src/std.pxd | ||
| src/util.pxd | ||
| tests/conftest.py | ||
| tests/conftest.py | ||
| tests/data/164957__zonkmachine__white-noise.ogg | ||
| tests/data/24741__tim-kahn__b23-c1-raw.aiff | ||
| tests/data/261590__kwahmah-02__little-glitch.flac | ||
| tests/data/353684__tec-studio__drip2.mp3 | ||
| tests/data/99642__jobro__deconvoluted-20hz-to-20khz.wav | ||
| tests/functional/test_context_management.py | ||
| tests/functional/test_examples.py | ||
| tests/functional/test_message_handling.py | ||
| tests/unit/conftest.py | ||
| tests/unit/fmath.py | ||
| tests/unit/test_context.py | ||
| tests/unit/test_effect.py | ||
| tests/unit/test_listener.py | ||
| tests/unit/test_source.py |
+2
-2
| Metadata-Version: 2.1 | ||
| Name: palace | ||
| Version: 0.2.0 | ||
| Version: 0.2.1 | ||
| Summary: Pythonic Audio Library and Codecs Environment | ||
| Home-page: https://github.com/McSinyx/palace | ||
| Home-page: https://mcsinyx.github.io/palace | ||
| Author: Nguyễn Gia Phong | ||
@@ -7,0 +7,0 @@ Author-email: mcsinyx@disroot.org |
+3
-3
| [metadata] | ||
| name = palace | ||
| version = 0.2.0 | ||
| url = https://github.com/McSinyx/palace | ||
| version = 0.2.1 | ||
| url = https://mcsinyx.github.io/palace | ||
| author = Nguyễn Gia Phong | ||
@@ -23,3 +23,3 @@ author_email = mcsinyx@disroot.org | ||
| license = LGPLv3+ | ||
| license_file = LICENSE | ||
| license_files = LICENSE | ||
| description = Pythonic Audio Library and Codecs Environment | ||
@@ -26,0 +26,0 @@ long_description = file: README.md |
+12
-6
| #!/usr/bin/env python3 | ||
| # setup script | ||
| # Copyright (C) 2019, 2020 Nguyễn Gia Phong | ||
| # Copyright (C) 2020 Francesco Caliumi | ||
| # | ||
@@ -24,3 +25,3 @@ # This file is part of palace. | ||
| from distutils.dir_util import mkpath | ||
| from distutils.errors import DistutilsFileError | ||
| from distutils.errors import DistutilsExecError, DistutilsFileError | ||
| from distutils.file_util import copy_file | ||
@@ -31,3 +32,3 @@ from operator import methodcaller | ||
| from platform import system | ||
| from subprocess import DEVNULL, PIPE, run | ||
| from subprocess import DEVNULL, PIPE, run, CalledProcessError | ||
@@ -60,6 +61,11 @@ from Cython.Build import cythonize | ||
| mkpath(self.build_temp) | ||
| copy_file(join(dirname(__file__), 'CMakeLists.txt'), | ||
| self.build_temp) | ||
| cmake = run(['cmake', '.'], check=True, stdout=DEVNULL, stderr=PIPE, | ||
| cwd=self.build_temp, universal_newlines=True) | ||
| copy_file(join(dirname(__file__), 'CMakeLists.txt'), self.build_temp) | ||
| try: | ||
| cmake = run( | ||
| ['cmake', '.'], check=True, stdout=DEVNULL, stderr=PIPE, | ||
| cwd=self.build_temp, universal_newlines=True) | ||
| except CalledProcessError as e: | ||
| log.error(e.stderr.strip()) | ||
| raise DistutilsExecError(str(e)) | ||
| for key, value in map(methodcaller('groups'), | ||
@@ -66,0 +72,0 @@ re.finditer(r'^alure2_(\w*)=(.*)$', |
+12
-9
@@ -109,6 +109,6 @@ # Cython declarations of alure | ||
| float room_rolloff_factor 'flRoomRolloffFactor' | ||
| int decay_hf_limit 'iDecayHFLimit' | ||
| bint decay_hf_limit 'iDecayHFLimit' | ||
| cdef cppclass EFXCHORUSPROPERTIES: | ||
| int waveform 'iWaveform' | ||
| bint waveform 'iWaveform' | ||
| int phase 'iPhase' | ||
@@ -145,5 +145,5 @@ float rate 'flRate' | ||
| # Enum classes: | ||
| cdef enum SampleType: | ||
| ctypedef enum SampleType: | ||
| pass | ||
| cdef enum ChannelConfig: | ||
| ctypedef enum ChannelConfig: | ||
| pass | ||
@@ -157,3 +157,3 @@ | ||
| cdef enum DeviceEnumeration: | ||
| ctypedef enum DeviceEnumeration: | ||
| Basic 'alure::DeviceEnumeration::Basic' | ||
@@ -163,3 +163,3 @@ Full 'alure::DeviceEnumeration::Full' | ||
| cdef enum DefaultDeviceType: | ||
| ctypedef enum DefaultDeviceType: | ||
| Basic 'alure::DefaultDeviceType::Basic' | ||
@@ -169,10 +169,10 @@ Full 'alure::DefaultDeviceType::Full' | ||
| cdef enum PlaybackName: | ||
| ctypedef enum PlaybackName: | ||
| Basic 'alure::PlaybackName::Basic' | ||
| Full 'alure::PlaybackName::Full' | ||
| cdef enum DistanceModel: | ||
| ctypedef enum DistanceModel: | ||
| pass | ||
| cdef enum Spatialize: | ||
| ctypedef enum Spatialize: | ||
| Off 'alure::Spatialize::Off' | ||
@@ -272,2 +272,3 @@ On 'alure::Spatialize::On' | ||
| void make_current 'MakeCurrent'(Context) except + | ||
| @staticmethod | ||
@@ -278,2 +279,3 @@ Context get_current 'GetCurrent'() except + | ||
| void make_thread_current 'MakeThreadCurrent'(Context) except + | ||
| @staticmethod | ||
@@ -528,2 +530,3 @@ Context get_thread_current 'GetThreadCurrent'() except + | ||
| unique_ptr[FileIOFactory] set(unique_ptr[FileIOFactory]) | ||
| @staticmethod | ||
@@ -530,0 +533,0 @@ FileIOFactory& get() |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
3311447
3.2%43
53.57%1724
184.96%