Split trigger/render_ms (#79)

Keep Config.trigger/render_width, but raise warning if encountered.
pull/357/head
nyanpasu64 2018-12-06 12:11:39 -08:00 zatwierdzone przez GitHub
rodzic 6d312f5de4
commit ffd92f4622
7 zmienionych plików z 124 dodań i 51 usunięć

Wyświetl plik

@ -56,10 +56,12 @@ class Channel:
# nsamp = orig / subsampling
# stride = subsampling * width
def calculate_nsamp(sub):
return round(ovgen_cfg.width_s * self.wave.smp_s / sub)
trigger_samp = calculate_nsamp(tsub)
self.render_samp = calculate_nsamp(rsub)
def calculate_nsamp(width_ms, sub):
width_s = width_ms / 1000
return round(width_s * self.wave.smp_s / sub)
trigger_samp = calculate_nsamp(ovgen_cfg.trigger_ms, tsub)
self.render_samp = calculate_nsamp(ovgen_cfg.render_ms, rsub)
self.trigger_stride = tsub * tw
self.render_stride = rsub * rw

Wyświetl plik

@ -5,7 +5,7 @@ from typing import Optional, List, Tuple, Union
import click
from ovgenpy.channel import ChannelConfig
from ovgenpy.config import OvgenError, yaml
from ovgenpy.config import yaml
from ovgenpy.outputs import IOutputConfig, FFplayOutputConfig, FFmpegOutputConfig
from ovgenpy.ovgenpy import default_config, Config, Ovgen
@ -157,10 +157,10 @@ def main(
cfg_dir = '.'
if show_gui:
raise OvgenError('GUI not implemented')
raise click.UsageError('GUI not implemented')
else:
if not files:
raise click.ClickException('Must specify files or folders to play')
raise click.UsageError('Must specify files or folders to play')
if write:
write_path = get_path(audio, YAML_NAME)
yaml.dump(cfg, write_path)

Wyświetl plik

@ -9,8 +9,8 @@ if TYPE_CHECKING:
__all__ = ['yaml',
'register_config', 'kw_config', 'Alias', 'Ignored',
'register_enum', 'OvgenError']
'register_config', 'kw_config', 'Alias', 'Ignored', 'register_enum',
'OvgenError', 'OvgenWarning']
# Setup YAML loading (yaml object).
@ -153,7 +153,13 @@ class _EnumMixin:
# Miscellaneous
class OvgenError(ValueError):
""" Error caused by invalid end-user input (via CLI or YAML config). """
""" Error caused by invalid end-user input (via YAML/GUI config).
(Should be) caught by GUI and displayed to user. """
pass
class OvgenWarning(UserWarning):
""" Warning about deprecated end-user config (YAML/GUI).
(Should be) caught by GUI and displayed to user. """
pass

Wyświetl plik

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import time
import warnings
from contextlib import ExitStack, contextmanager
from enum import unique, IntEnum
from fractions import Fraction
@ -10,7 +11,7 @@ import attr
from ovgenpy import outputs as outputs_
from ovgenpy.channel import Channel, ChannelConfig
from ovgenpy.config import kw_config, register_enum, Ignored
from ovgenpy.config import kw_config, register_enum, Ignored, OvgenError, OvgenWarning
from ovgenpy.renderer import MatplotlibRenderer, RendererConfig
from ovgenpy.layout import LayoutConfig
from ovgenpy.triggers import ITriggerConfig, CorrelationTriggerConfig, PerFrameCache
@ -41,7 +42,9 @@ class Config:
fps: int
width_ms: int
trigger_ms: Optional[int] = None
render_ms: Optional[int] = None
_width_ms: Optional[int] = None
# trigger_subsampling and render_subsampling override subsampling.
# Always non-None after __attrs_post_init__()
@ -54,6 +57,8 @@ class Config:
render_fps = property(lambda self:
Fraction(self.fps, self.render_subfps))
# TODO: Remove cfg._width (breaks compat)
# ISSUE: baking into trigger_ms will stack with channel-specific ms
trigger_width: int = 1
render_width: int = 1
@ -78,17 +83,13 @@ class Config:
wav_prefix = Ignored
# endregion
@property
def width_s(self) -> float:
return self.width_ms / 1000
def __attrs_post_init__(self):
# Cast benchmark_mode to enum.
try:
if not isinstance(self.benchmark_mode, BenchmarkMode):
self.benchmark_mode = BenchmarkMode[self.benchmark_mode]
except KeyError:
raise ValueError(
raise OvgenError(
f'invalid benchmark_mode mode {self.benchmark_mode} not in '
f'{[el.name for el in BenchmarkMode]}')
@ -96,6 +97,24 @@ class Config:
subsampling = self._subsampling
self.trigger_subsampling = coalesce(self.trigger_subsampling, subsampling)
self.render_subsampling = coalesce(self.render_subsampling, subsampling)
# Compute trigger_ms and render_ms.
width_ms = self._width_ms
try:
self.trigger_ms = coalesce(self.trigger_ms, width_ms)
self.render_ms = coalesce(self.render_ms, width_ms)
except TypeError:
raise OvgenError(
'Must supply either width_ms or both (trigger_ms and render_ms)')
deprecated = []
if self.trigger_width != 1:
deprecated.append('trigger_width')
if self.render_width != 1:
deprecated.append('render_width')
if deprecated:
warnings.warn(f"Options {deprecated} are deprecated and will be removed",
OvgenWarning)
_FPS = 60 # f_s
@ -108,7 +127,8 @@ def default_config(**kwargs) -> Config:
fps=_FPS,
amplification=1,
width_ms=40,
trigger_ms=40,
render_ms=40,
trigger_subsampling=1,
render_subsampling=2,
trigger=CorrelationTriggerConfig(

Wyświetl plik

@ -7,7 +7,7 @@ from scipy import signal
from scipy.signal import windows
import attr
from ovgenpy.config import kw_config, OvgenError, Alias
from ovgenpy.config import kw_config, OvgenError, Alias, OvgenWarning
from ovgenpy.util import find, obj_name
from ovgenpy.utils.windows import midpad, leftpad
from ovgenpy.wave import FLOAT
@ -140,7 +140,8 @@ class CorrelationTriggerConfig(ITriggerConfig):
if self.post:
warnings.warn(
"Ignoring old `CorrelationTriggerConfig.use_edge_trigger` flag, "
"overriden by newer `post` flag."
"overriden by newer `post` flag.",
OvgenWarning
)
else:
self.post = ZeroCrossingTriggerConfig()

Wyświetl plik

@ -9,7 +9,7 @@ setup(
author='nyanpasu64',
author_email='',
description='',
tests_require=['pytest', 'pytest-pycharm', 'hypothesis', 'delayed-assert'],
tests_require=['pytest>=3.2.0', 'pytest-pycharm', 'hypothesis', 'delayed-assert'],
install_requires=[
'numpy', 'scipy', 'click', 'ruamel.yaml',
'matplotlib',

Wyświetl plik

@ -9,25 +9,48 @@ from pytest_mock import MockFixture
import ovgenpy.channel
import ovgenpy.ovgenpy
from ovgenpy.channel import ChannelConfig, Channel
from ovgenpy.config import OvgenError
from ovgenpy.ovgenpy import default_config, Ovgen, BenchmarkMode
from ovgenpy.triggers import NullTriggerConfig
from ovgenpy.util import coalesce
positive = hs.integers(min_value=1, max_value=100)
maybe = hs.one_of(positive, hs.none())
@given(subsampling=positive, tsub=maybe, rsub=maybe,
trigger_width=positive, render_width=positive)
def test_channel_subsampling(
subsampling: int,
tsub: Optional[int],
rsub: Optional[int],
trigger_width: int,
render_width: int,
mocker: MockFixture
positive = hs.integers(min_value=1, max_value=100)
Positive = int
# In order to get good shrinking behaviour, try to put simpler strategies first.
maybe = hs.one_of(hs.none(), positive)
Maybe = Optional[int]
@pytest.mark.filterwarnings("ignore::ovgenpy.config.OvgenWarning")
@given(
# Channel
c_trigger_width=maybe, c_render_width=maybe,
# Global
width_ms=maybe, trigger_ms=maybe, render_ms=maybe,
subsampling=positive, tsub=maybe, rsub=maybe,
g_trigger_width=positive, g_render_width=positive,
)
def test_config_channel_width_stride(
# Channel
c_trigger_width: Maybe, c_render_width: Maybe,
# Global
width_ms: Maybe, trigger_ms: Maybe, render_ms: Maybe,
subsampling: Positive, tsub: Maybe, rsub: Maybe,
g_trigger_width: Positive, g_render_width: Positive,
mocker: MockFixture,
):
""" Ensure trigger/render_samp and trigger/render subsampling
are computed correctly. """
""" (Tautologically) verify:
- cfg.t/r_ms (given width_ms)
- channel. r_samp (given cfg)
- channel.t/r_stride (given cfg.sub/width and cfg.width)
- trigger._tsamp, _stride
- renderer's method calls(samp, stride)
"""
# region setup test variables
ovgenpy.ovgenpy.PRINT_TIMESTAMP = False # Cleanup Hypothesis testing logs
@ -44,35 +67,56 @@ def test_channel_subsampling(
ccfg = ChannelConfig(
'tests/sine440.wav',
trigger_width=trigger_width,
render_width=render_width,
trigger_width=c_trigger_width,
render_width=c_render_width,
)
cfg = default_config(
channels=[ccfg],
subsampling=subsampling,
trigger_subsampling=tsub,
render_subsampling=rsub,
trigger=NullTriggerConfig(),
benchmark_mode=BenchmarkMode.OUTPUT
)
channel = Channel(ccfg, cfg)
def get_cfg():
return default_config(
width_ms=width_ms,
trigger_ms=trigger_ms,
render_ms=render_ms,
subsampling=subsampling,
trigger_subsampling=tsub,
render_subsampling=rsub,
trigger_width=g_trigger_width,
render_width=g_render_width,
channels=[ccfg],
trigger=NullTriggerConfig(),
benchmark_mode=BenchmarkMode.OUTPUT
)
# endregion
if not (width_ms or (trigger_ms and render_ms)):
with pytest.raises(OvgenError):
_cfg = get_cfg()
return
cfg = get_cfg()
channel = Channel(ccfg, cfg)
# Ensure cfg.width_ms etc. are correct
assert cfg.trigger_ms == coalesce(trigger_ms, width_ms)
assert cfg.render_ms == coalesce(render_ms, width_ms)
# Ensure channel.window_samp, trigger_subsampling, render_subsampling are correct.
tsub = coalesce(tsub, subsampling)
rsub = coalesce(rsub, subsampling)
def ideal_samp(sub):
def ideal_samp(width_ms, sub):
width_s = width_ms / 1000
return pytest.approx(
round(cfg.width_s * channel.wave.smp_s / sub), abs=1)
round(width_s * channel.wave.smp_s / sub), rel=1e-6)
ideal_tsamp = ideal_samp(tsub)
ideal_rsamp = ideal_samp(rsub)
ideal_tsamp = ideal_samp(cfg.trigger_ms, tsub)
ideal_rsamp = ideal_samp(cfg.render_ms, rsub)
assert channel.render_samp == ideal_rsamp
del subsampling
assert channel.trigger_stride == tsub * trigger_width
assert channel.render_stride == rsub * render_width
assert channel.trigger_stride == tsub * coalesce(c_trigger_width, g_trigger_width)
assert channel.render_stride == rsub * coalesce(c_render_width, g_render_width)
## Ensure trigger uses channel.window_samp and trigger_stride.
trigger = channel.trigger