kopia lustrzana https://github.com/corrscope/corrscope
Split trigger/render_ms (#79)
Keep Config.trigger/render_width, but raise warning if encountered.pull/357/head
rodzic
6d312f5de4
commit
ffd92f4622
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
2
setup.py
2
setup.py
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue